Repository: tus/tusd (Go)

Issue:
Support custom headers for CORS
I'm using the oficial JS client with the oficial go server, the problem is that I need to send a custom header when it's JS code, and the [tusd middleware](https://github.com/tus/tusd/blob/master/unrouted_handler.go#L140) has fixed CORS headers.

It would be good to have a way to set custom CORS headers.
how to set/overwrite cors in the tusd code provided in the doc
**Question**
I'm not a GO developer (I even did my best to learn GO basics just in the hope to make it works) but after wasting days without luck, I will do my last try to find an answer, otherwise, I'm giving up all the GO story because it just hurts to burn all the energy without finding the simplest answer: can just someone tell how to set cors to a specific host (i.e: example.com) instead of the default *  without using a proxy

**Setup details**

Here is the example from the doc (we just need a way to overwrite the "Access-Control-Allow-Origin", I browsed the whole internet and can't find someone answering it !!!

```
package main

import (
	"fmt"
	"net/http"

	"github.com/tus/tusd/pkg/filestore"
	tusd "github.com/tus/tusd/pkg/handler"
)

func main() {
	// Create a new FileStore instance which is responsible for
	// storing the uploaded file on disk in the specified directory.
	// This path _must_ exist before tusd will store uploads in it.
	// If you want to save them on a different medium, for example
	// a remote FTP server, you can implement your own storage backend
	// by implementing the tusd.DataStore interface.
	store := filestore.FileStore{
		Path: "./uploads",
	}

	// A storage backend for tusd may consist of multiple different parts which
	// handle upload creation, locking, termination and so on. The composer is a
	// place where all those separated pieces are joined together. In this example
	// we only use the file store but you may plug in multiple.
	composer := tusd.NewStoreComposer()
	store.UseIn(composer)

	// Create a new HTTP handler for the tusd server by providing a configuration.
	// The StoreComposer property must be set to allow the handler to function.
	handler, err := tusd.NewHandler(tusd.Config{
		BasePath:              "/files/",
		StoreComposer:         composer,
		NotifyCompleteUploads: true,
	})
	if err != nil {
		panic(fmt.Errorf("Unable to create handler: %s", err))
	}

	// Start another goroutine for receiving events from the handler whenever
	// an upload is completed. The event will contains details about the upload
	// itself and the relevant HTTP request.
	go func() {
		for {
			event := <-handler.CompleteUploads
			fmt.Printf("Upload %s finished\n", event.Upload.ID)
		}
	}()

	// Right now, nothing has happened since we need to start the HTTP server on
	// our own. In the end, tusd will start listening on and accept request at
	// http://localhost:8080/files
	http.Handle("/files/", http.StripPrefix("/files/", handler))
	err = http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(fmt.Errorf("Unable to listen: %s", err))
	}
}
```


Discussion: I would recommend you to use a proxy in front of tusd which sets these headers, such as nginx or Apache. This allows for more complex header constructions, than tusd could ever provide.

On the other hand, if you use tusd as a Go package, this discussion shows you how to modify the CORS headers: https://github.com/tus/tusd/pull/123
I'm using nginx and I also use tusd as a go package.

The problem is that you can't set `Access-Control-Allow-Origin` with ` *` for example, because tuds middleware adds `Access-Control-Allow-Origin` with the current `Origin` header.

I don't fell that assuming fixed CORS headers is a good solution, it could have it as default, but it would be good to allow changing it.
> The problem is that you can't set Access-Control-Allow-Origin with * for example, because tuds middleware adds Access-Control-Allow-Origin with the current Origin header.

Why would you want to use `*` as the value? Using the Origin header should be sufficient as it indicates the domain of the requesting site.

> I don't fell that assuming fixed CORS headers is a good solution, it could have it as default, but it would be good to allow changing it.

I disagree. IMO, it's not tusd's responsibility to provide the headers for the rest of your application. Are you having issues with setting the headers using nginx?
`*` was just an example.

What I want is to disable CORS on tusd because I want to set all CORS headers on nginx. I can't set `Access-Control-Allow-Origin` twice for example.

For example, my server doesn't allow DELETE requests, and the default CORS config allows it. It's ok to set all CORS headers if you use tuds as a client, but as a library I see this as a limitation.
> What I want is to disable CORS on tusd because I want to set all CORS headers on nginx. I can't set Access-Control-Allow-Origin twice for example.

Nginx should allow you to overwrite headers which are already set, see https://stackoverflow.com/questions/14501047/how-to-add-a-response-header-on-nginx-when-using-proxy-pass

> For example, my server doesn't allow DELETE requests, and the default CORS config allows it.

I agree that this is suboptimal but as far as I know this should not cause any technical issues or does it?
To overwrite headers with nginx I need an extra module that isn't part of nginx. This won't work for me.

It doesn't cause any problem at all, it's just something that I would like to take control of.

My tusd server has OAuth, so it's a little bit hard to customize things sometimes. It would be nice to have some extra features to allow to use tusd as a customizable library.

I need to wrap things and do some hacks to get things working because I have a middleware before "tusd hadlers" to check if the user is authenticated.

For example, here is a small code of what I do:

```go
mux.Handle("/uploads/", func(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if origin := r.Header.Get("Origin"); origin != "" {
			header := w.Header()
			header.Set("Access-Control-Allow-Origin", origin)

			if r.Method == "OPTIONS" {
				// Preflight request
				header.Add("Access-Control-Allow-Methods", "POST, GET, HEAD, PATCH, DELETE, OPTIONS")
				header.Add("Access-Control-Allow-Headers", "Origin, Authorization, X-Requested-With, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata")
				header.Set("Access-Control-Max-Age", "86400")
			} else {
				// Actual request
				header.Add("Access-Control-Expose-Headers", "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata")
			}

			// Delete Origin to disable tusd CORS,
			// because we already set all CORS headers needed.
			r.Header.Del("Origin")
		}

		if r.Method == "OPTIONS" {
			next.ServeHTTP(w, r)
		} else {
			if ok := UserAuthenticated(r); ok {
				next.ServeHTTP(w, r)
			} else {
				w.WriteHeader(http.StatusUnauthorized)
			}
		}
	})
}(http.StripPrefix("/uploads/", tusHandler)))
```
I agree with the OP of this issue, its difficult to get this running with CORS as it currently stands. I dont understand why tusd needs to be adding CORS headers when its in proxy mode?
> I dont understand why tusd needs to be adding CORS headers when its in proxy mode?

We use tusd behind a proxy and rely on tusd setting the correct CORS headers as it knows best which headers a necessary. So I think the situation depends highly on your setup.

So, @andrewmclagan, what are you suggesting? Would a flag to not set any CORS headers help? If so, we are happy to accept a PR for this.
Hello :)

I may be wrong in assuming this, although generally its been my experience that when using a forward proxy there is an assumption that all network related headers would be over-taken by the proxy? Whats your thoughts / approach?


> I may be wrong in assuming this, although generally its been my experience that when using a forward proxy there is an assumption that all network related headers would be over-taken by the proxy?

Personally, I have never heard of such a rule but that doesn't mean it's not a best practice. What is your "definition" of network related headers? For me, this refers to headers such as `Connection: close` or `Cache-Control`. Both of which should (in most situation) be handled by the proxy.

But the CORS headers (`Access-Control-*`) are related to the application rather than the network in my mind. For example, the application knows best what headers it can safely expose to the browser (i.e. Access-Control-Expose-Headers) and this is not the proxies' matter. Of course, the proxy can still modify and extend these headers if it needs to do so for supporting other parts of the system but this is not tusd's job either.

For me, both tusd and the proxy can be responsible for setting the CORS headers and this responsibility is shared between these two components.
That seems like acceptable login to me

I am sorry to hear that you are struggling so much with tusd. However, there is currently no way to change or overwrite the CORS setup from tusd. Neither programmatically nor using the tusd binary. The only option right now it to use an external proxy, such as nginx. Since this is an often requested feature, we are always open for PRs to improve in this regard :)
@Acconut if only this was written in the doc, you would save me tons of time and struggles  I was able to set CORS via Nginx but the upload is not resumable even though I added:


        # Disable request and response buffering
        proxy_request_buffering  off;
        proxy_buffering          off;
        proxy_http_version       1.1;

        # Add X-Forwarded-* headers
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;


here is a live demo: ********* (demo link deleted) I use uppy with resume: true, when I pause and I resume, it starts again from the very beginning. 
Could it be that there is another proxy between the browser and your nginx before tusd? Maybe from your hosting provider? It looks like this proxy buffers the request from a brief look.
@Acconut yeah you are right, it seems that the url they provided me with was using some kind of proxy/load-balancer, once I added an IPv4 it started working finally :) I'm ready now to upload files, but most importantly I will be sure to write a blog post about all the challenges I faced during the implementation, hopefully, I can save people like me some time... 

just one last piece, as I'm using tusd programmatically, any method I can verify the upload is authorized? 
You can use the PreCreateCallback in the config struct: https://godoc.org/github.com/tus/tusd/pkg/handler#Config

> I will be sure to write a blog post about all the challenges I faced during the implementation, hopefully, I can save people like me some time...

Feel free to send us the link as well, so we can try to improve in these areas :)
@Acconut yeah I see the PreUploadCreateCallback... as I'm totally new to GO I have to take a little time to figure out how to use it with the necessary verifications.

I will make sure to send the URL once I get it done. Thank you very much for the guidance, you just saved me an extra 3 days (lol) :+1: 
Could you please consider adding a tusd flag to enable `Access-Control-Allow-Credentials` CORS header? 

Following [this issue](https://github.com/tus/tus-js-client/issues/92) we enabled `withCredentials` [with the example provided in tus-js-client docs](https://github.com/tus/tus-js-client/blob/master/docs/api.md#onbeforerequest) on our client, so we can forward loadbalancer (AWS's ALB) session cookie, but now we're getting this error: 
```
Access to XMLHttpRequest at 'https://example.com:1080/files/' from origin 'https://app.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
```

Without cookies tus requests are distributed randomly to our tusd instances, which caused [the corruption mentioned here](https://github.com/tus/tusd/issues/290#issuecomment-513236180).

We're not running, nor planning to run another reverse-proxy between the loadbalancer and tusd instance, adding nginx just to add this header feels like trying to kill a fly with a bazooka, especially given that this exact use-case seems to be recommended thorough the tus projects :) 

I'm not a gopher, but adding a flag and conditionally adding the allow-credentials header [here](https://github.com/kaleworsley/tusd/blob/master/unrouted_handler.go#L138) doesn't sound that hard? Would that be something you'd consider merging if I prepared a PR?

Thanks!
> Following this issue we enabled withCredentials with the example provided in tus-js-client docs on our client, so we can forward loadbalancer (AWS's ALB) session cookie, but now we're getting this error:

That's a good reason, I never came across before.

I am open to adding options for customizing the CORS header sent by tusd since this is a popular issue. So feel free to open a PR. However, it would be helpful if we have a concept for not only customizing `Access-Control-Allow-Credentials` but also the other headers. So, if you make a PR please put some thoughts into how other headers could be customized in the future as well :)
@Acconut noted, I'll consider.

This seems like a bigger issue though. In meanwhile I got to the same issue with cookies in our react-native app, which goes deep through react-native-tus-client, tus-android-client down to this tus-java-client PR [here, pointing that cookies are not passed](https://github.com/tus/tus-java-client/pull/18) and the related discussion [about changing http stack/client](https://github.com/tus/tus-java-client/issues/14).
I'm considering avoiding the issue (and adding support in 2 languages I don't know) by just moving the instance out of the scaled service to a single node.
Oh right, thanks for letting us know that the Java client does not support Cookies. As I said, people are apparently rarely using Cookies with tusd, so this is a bit of untested territory. 



Fix (patch):
diff --git a/cmd/tusd/cli/flags.go b/cmd/tusd/cli/flags.go
index 1cc1e4b45..679972909 100644
--- a/cmd/tusd/cli/flags.go
+++ b/cmd/tusd/cli/flags.go
@@ -24,6 +24,12 @@ var Flags struct {
 	DisableDownload         bool
 	DisableTermination      bool
 	DisableCors             bool
+	CorsAllowOrigin         string
+	CorsAllowCredentials    bool
+	CorsAllowMethods        string
+	CorsAllowHeaders        string
+	CorsMaxAge              string
+	CorsExposeHeaders       string
 	Timeout                 int64
 	S3Bucket                string
 	S3ObjectPrefix          string
@@ -75,6 +81,12 @@ func ParseFlags() {
 	flag.BoolVar(&Flags.DisableDownload, "disable-download", false, "Disable the download endpoint")
 	flag.BoolVar(&Flags.DisableTermination, "disable-termination", false, "Disable the termination endpoint")
 	flag.BoolVar(&Flags.DisableCors, "disable-cors", false, "Disable CORS headers")
+	flag.StringVar(&Flags.CorsAllowOrigin, "cors-allow-origin", ".*", "Regular expression used to determine if the Origin header is allowed. If not, no CORS headers will be sent. By default, all origins are allowed.")
+	flag.BoolVar(&Flags.CorsAllowCredentials, "cors-allow-credentials", false, "Allow credentials by setting Access-Control-Allow-Credentials: true")
+	flag.StringVar(&Flags.CorsAllowMethods, "cors-allow-methods", "", "Comma-separated list of request methods that are included in Access-Control-Allow-Methods in addition to the ones required by tusd")
+	flag.StringVar(&Flags.CorsAllowHeaders, "cors-allow-headers", "", "Comma-separated list of headers that are included in Access-Control-Allow-Headers in addition to the ones required by tusd")
+	flag.StringVar(&Flags.CorsMaxAge, "cors-max-age", "86400", "Value of the Access-Control-Max-Age header to control the cache duration of CORS responses.")
+	flag.StringVar(&Flags.CorsExposeHeaders, "cors-expose-headers", "", "Comma-separated list of headers that are included in Access-Control-Expose-Headers in addition to the ones required by tusd")
 	flag.Int64Var(&Flags.Timeout, "timeout", 6*1000, "Read timeout for connections in milliseconds.  A zero value means that reads will not timeout")
 	flag.StringVar(&Flags.S3Bucket, "s3-bucket", "", "Use AWS S3 with this bucket as storage backend (requires the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables to be set)")
 	flag.StringVar(&Flags.S3ObjectPrefix, "s3-object-prefix", "", "Prefix for S3 object names")
diff --git a/cmd/tusd/cli/serve.go b/cmd/tusd/cli/serve.go
index b4619ccbc..868881632 100644
--- a/cmd/tusd/cli/serve.go
+++ b/cmd/tusd/cli/serve.go
@@ -4,6 +4,7 @@ import (
 	"crypto/tls"
 	"net"
 	"net/http"
+	"regexp"
 	"strings"
 	"time"
 
@@ -26,11 +27,11 @@ func Serve() {
 	config := handler.Config{
 		MaxSize:                    Flags.MaxSize,
 		BasePath:                   Flags.Basepath,
+		Cors:                       getCorsConfig(),
 		RespectForwardedHeaders:    Flags.BehindProxy,
 		EnableExperimentalProtocol: Flags.ExperimentalProtocol,
 		DisableDownload:            Flags.DisableDownload,
 		DisableTermination:         Flags.DisableTermination,
-		DisableCors:                Flags.DisableCors,
 		StoreComposer:              Composer,
 		NotifyCompleteUploads:      true,
 		NotifyTerminatedUploads:    true,
@@ -166,3 +167,30 @@ func Serve() {
 		stderr.Fatalf("Unable to serve: %s", err)
 	}
 }
+
+func getCorsConfig() *handler.CorsConfig {
+	config := handler.DefaultCorsConfig
+	config.Disable = Flags.DisableCors
+	config.AllowCredentials = Flags.CorsAllowCredentials
+	config.MaxAge = Flags.CorsMaxAge
+
+	var err error
+	config.AllowOrigin, err = regexp.Compile(Flags.CorsAllowOrigin)
+	if err != nil {
+		stderr.Fatalf("Invalid regular expression for -cors-allow-origin flag: %s", err)
+	}
+
+	if Flags.CorsAllowHeaders != "" {
+		config.AllowHeaders += ", " + Flags.CorsAllowHeaders
+	}
+
+	if Flags.CorsAllowMethods != "" {
+		config.AllowMethods += ", " + Flags.CorsAllowMethods
+	}
+
+	if Flags.CorsExposeHeaders != "" {
+		config.ExposeHeaders += ", " + Flags.CorsExposeHeaders
+	}
+
+	return &config
+}
diff --git a/pkg/handler/config.go b/pkg/handler/config.go
index b53b9fc75..8c5bd7a06 100644
--- a/pkg/handler/config.go
+++ b/pkg/handler/config.go
@@ -5,6 +5,7 @@ import (
 	"log"
 	"net/url"
 	"os"
+	"regexp"
 )
 
 // Config provides a way to configure the Handler depending on your needs.
@@ -34,7 +35,14 @@ type Config struct {
 	DisableTermination bool
 	// Disable cors headers. If set to true, tusd will not send any CORS related header.
 	// This is useful if you have a proxy sitting in front of tusd that handles CORS.
+	//
+	// Deprecated: All CORS-related settings are available in via the Cors field. Use
+	// Cors.Disable instead of DisableCors.
 	DisableCors bool
+	// Cors can be used to customize the handling of Cross-Origin Resource Sharing (CORS).
+	// See the CorsConfig struct for more details.
+	// Defaults to DefaultCorsConfig.
+	Cors *CorsConfig
 	// NotifyCompleteUploads indicates whether sending notifications about
 	// completed uploads using the CompleteUploads channel should be enabled.
 	NotifyCompleteUploads bool
@@ -64,6 +72,48 @@ type Config struct {
 	PreFinishResponseCallback func(hook HookEvent) error
 }
 
+// CorsConfig provides a way to customize the the handling of Cross-Origin Resource Sharing (CORS).
+// More details about CORS are available at https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.
+type CorsConfig struct {
+	// Disable instructs the handler to ignore all CORS-related headers and never set a
+	// CORS-related header in a response. This is useful if CORS is already handled by a proxy.
+	Disable bool
+	// AllowOrigin is a regular expression used to check if a request is allowed to participate in the
+	// CORS protocol. If the request's Origin header matches the regular expression, CORS is allowed.
+	// If not, a 403 Forbidden response is sent, rejecting the CORS request.
+	AllowOrigin *regexp.Regexp
+	// AllowCredentials defines whether the `Access-Control-Allow-Credentials: true` header should be
+	// included in CORS responses. This allows clients to share credentials using the Cookie and
+	// Authorization header
+	AllowCredentials bool
+	// AllowMethods defines the value for the `Access-Control-Allow-Methods` header in the response to
+	// preflight requests. You can add custom methods here, but make sure that all tus-specific methods
+	// from DefaultConfig.AllowMethods are included as well.
+	AllowMethods string
+	// AllowHeaders defines the value for the `Access-Control-Allow-Headers` header in the response to
+	// preflight requests. You can add custom headers here, but make sure that all tus-specific header
+	// from DefaultConfig.AllowHeaders are included as well.
+	AllowHeaders string
+	// MaxAge defines the value for the `Access-Control-Max-Age` header in the response to preflight
+	// requests.
+	MaxAge string
+	// ExposeHeaders defines the value for the `Access-Control-Expose-Headers` header in the response to
+	// actual requests. You can add custom headers here, but make sure that all tus-specific header
+	// from DefaultConfig.ExposeHeaders are included as well.
+	ExposeHeaders string
+}
+
+// DefaultCorsConfig is the configuration that will be used in none is provided.
+var DefaultCorsConfig = CorsConfig{
+	Disable:          false,
+	AllowOrigin:      regexp.MustCompile(".*"),
+	AllowCredentials: false,
+	AllowMethods:     "POST, HEAD, PATCH, OPTIONS, GET, DELETE",
+	AllowHeaders:     "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version",
+	MaxAge:           "86400",
+	ExposeHeaders:    "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version",
+}
+
 func (config *Config) validate() error {
 	if config.Logger == nil {
 		config.Logger = log.New(os.Stdout, "[tusd] ", log.Ldate|log.Lmicroseconds)
@@ -95,5 +145,14 @@ func (config *Config) validate() error {
 		return errors.New("tusd: StoreComposer in Config needs to contain a non-nil core")
 	}
 
+	if config.Cors == nil {
+		config.Cors = &DefaultCorsConfig
+	}
+
+	// Support previous settings for disabling CORS.
+	if config.DisableCors {
+		config.Cors.Disable = true
+	}
+
 	return nil
 }
diff --git a/pkg/handler/unrouted_handler.go b/pkg/handler/unrouted_handler.go
index e48757e19..0e0ab1795 100644
--- a/pkg/handler/unrouted_handler.go
+++ b/pkg/handler/unrouted_handler.go
@@ -72,6 +72,7 @@ var (
 	ErrUploadLengthAndUploadDeferLength = NewHTTPError(errors.New("provided both Upload-Length and Upload-Defer-Length"), http.StatusBadRequest)
 	ErrInvalidUploadDeferLength         = NewHTTPError(errors.New("invalid Upload-Defer-Length header"), http.StatusBadRequest)
 	ErrUploadStoppedByServer            = NewHTTPError(errors.New("upload has been stopped by server"), http.StatusBadRequest)
+	ErrOriginNotAllowed                 = NewHTTPError(errors.New("request origin is not allowed"), http.StatusForbidden)
 
 	errReadTimeout     = errors.New("read tcp: i/o timeout")
 	errConnectionReset = errors.New("read tcp: connection reset by peer")
@@ -213,9 +214,9 @@ func (handler *UnroutedHandler) SupportedExtensions() string {
 func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		// Allow overriding the HTTP method. The reason for this is
-		// that some libraries/environments to not support PATCH and
-		// DELETE requests, e.g. Flash in a browser and parts of Java
-		if newMethod := r.Header.Get("X-HTTP-Method-Override"); newMethod != "" {
+		// that some libraries/environments do not support PATCH and
+		// DELETE requests, e.g. Flash in a browser and parts of Java.
+		if newMethod := r.Header.Get("X-HTTP-Method-Override"); r.Method == "POST" && newMethod != "" {
 			r.Method = newMethod
 		}
 
@@ -225,27 +226,29 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
 
 		header := w.Header()
 
-		if origin := r.Header.Get("Origin"); !handler.config.DisableCors && origin != "" {
-			header.Set("Access-Control-Allow-Origin", origin)
+		cors := handler.config.Cors
+		if origin := r.Header.Get("Origin"); !cors.Disable && origin != "" {
+			originIsAllowed := cors.AllowOrigin.MatchString(origin)
+			if !originIsAllowed {
+				handler.sendError(w, r, ErrOriginNotAllowed)
+				return
+			}
 
-			if r.Method == "OPTIONS" {
-				allowedMethods := "POST, HEAD, PATCH, OPTIONS"
-				if !handler.config.DisableDownload {
-					allowedMethods += ", GET"
-				}
+			header.Set("Access-Control-Allow-Origin", origin)
+			header.Set("Vary", "Origin")
 
-				if !handler.config.DisableTermination {
-					allowedMethods += ", DELETE"
-				}
+			if cors.AllowCredentials {
+				header.Add("Access-Control-Allow-Credentials", "true")
+			}
 
+			if r.Method == "OPTIONS" {
 				// Preflight request
-				header.Add("Access-Control-Allow-Methods", allowedMethods)
-				header.Add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version")
-				header.Set("Access-Control-Max-Age", "86400")
-
+				header.Add("Access-Control-Allow-Methods", cors.AllowMethods)
+				header.Add("Access-Control-Allow-Headers", cors.AllowHeaders)
+				header.Set("Access-Control-Max-Age", cors.MaxAge)
 			} else {
 				// Actual request
-				header.Add("Access-Control-Expose-Headers", "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version")
+				header.Add("Access-Control-Expose-Headers", cors.ExposeHeaders)
 			}
 		}