Skip to content

Commit

Permalink
cli, handler: Allow to customize CORS handling (#997)
Browse files Browse the repository at this point in the history
* feat: Add flag to control CORS Origin header (#987)

* added flags for CORS header

* go fmt

* fix: go vet

* fix: check Origin to match configured CORS Origin

---------

Co-authored-by: Sean Macdonald <[email protected]>

* handler: Restrict method overriding to POST requests

* handler: Implement more CORS options

* Add back support for DisableCors

* Add flags for CORS options

* Add documentation

* Fix flag description

* Remove now unneeded -cors-origin flag

---------

Co-authored-by: Thomas Müller <[email protected]>
Co-authored-by: Sean Macdonald <[email protected]>
  • Loading branch information
3 people authored Sep 6, 2023
1 parent ee90db0 commit a1e1fdf
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 58 deletions.
12 changes: 12 additions & 0 deletions cmd/tusd/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
30 changes: 29 additions & 1 deletion cmd/tusd/cli/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/tls"
"net"
"net/http"
"regexp"
"strings"
"time"

Expand All @@ -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,
Expand Down Expand Up @@ -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
}
59 changes: 59 additions & 0 deletions pkg/handler/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"net/url"
"os"
"regexp"
)

// Config provides a way to configure the Handler depending on your needs.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit a1e1fdf

Please sign in to comment.