diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 2e48d1c9a1..4442d4b43f 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -97,7 +97,7 @@ The following example is prohibited because it can expose your application to se ```go app.Use(cors.New(cors.Config{ - AllowOrigins: "*", + AllowOrigins: []string{"*"}, AllowCredentials: true, })) ``` @@ -105,43 +105,46 @@ app.Use(cors.New(cors.Config{ This will result in the following panic: ``` -panic: [CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to `"*"`. +panic: [CORS] Configuration error: When 'AllowCredentials' is set to true, 'AllowOrigins' cannot contain a wildcard origin '*'. Please specify allowed origins explicitly or adjust 'AllowCredentials' setting. ``` ## Config -| Property | Type | Description | Default | -|:---------------------|:----------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------| -| AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard (`"*"`) to prevent security vulnerabilities. | `false` | -| AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | -| AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | -| AllowOrigins | `string` | AllowOrigins defines a comma-separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | -| AllowOriginsFunc | `func(origin string) bool` | `AllowOriginsFunc` is a function that dynamically determines whether to allow a request based on its origin. If this function returns `true`, the 'Access-Control-Allow-Origin' response header will be set to the request's 'origin' header. This function is only used if the request's origin doesn't match any origin in `AllowOrigins`. | `nil` | -| AllowPrivateNetwork | `bool` | Indicates whether the `Access-Control-Allow-Private-Network` response header should be set to `true`, allowing requests from private networks. This aligns with modern security practices for web applications interacting with private networks. | `false` | -| ExposeHeaders | `string` | ExposeHeaders defines whitelist headers that clients are allowed to access. | `""` | -| MaxAge | `int` | MaxAge indicates how long (in seconds) the results of a preflight request can be cached. If you pass MaxAge 0, the Access-Control-Max-Age header will not be added and the browser will use 5 seconds by default. To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header to 0. | `0` | -| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | - +| Property | Type | Description | Default | +|:---------------------|:----------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------| +| AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard (`"*"`) to prevent security vulnerabilities. | `false` | +| AllowHeaders | `[]string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `[]` | +| AllowMethods | `[]string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET, POST, HEAD, PUT, DELETE, PATCH"` | +| AllowOrigins | `[]string` | AllowOrigins defines a list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. If the special wildcard `"*"` is present in the list, all origins will be allowed. | `["*"]` | +| AllowOriginsFunc | `func(origin string) bool` | `AllowOriginsFunc` is a function that dynamically determines whether to allow a request based on its origin. If this function returns `true`, the 'Access-Control-Allow-Origin' response header will be set to the request's 'origin' header. This function is only used if the request's origin doesn't match any origin in `AllowOrigins`. | `nil` | +| AllowPrivateNetwork | `bool` | Indicates whether the `Access-Control-Allow-Private-Network` response header should be set to `true`, allowing requests from private networks. This aligns with modern security practices for web applications interacting with private networks. | `false` | +| ExposeHeaders | `string` | ExposeHeaders defines whitelist headers that clients are allowed to access. | `[]` | +| MaxAge | `int` | MaxAge indicates how long (in seconds) the results of a preflight request can be cached. If you pass MaxAge 0, the Access-Control-Max-Age header will not be added and the browser will use 5 seconds by default. To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header to 0. | `0` | +| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | + +:::note +If AllowOrigins is a zero value `[]string{}`, and AllowOriginsFunc is provided, the middleware will not default to allowing all origins with the wildcard value "*". Instead, it will rely on the AllowOriginsFunc to dynamically determine whether to allow a request based on its origin. This provides more flexibility and control over which origins are allowed. +::: ## Default Config ```go var ConfigDefault = Config{ - Next: nil, + Next: nil, AllowOriginsFunc: nil, - AllowOrigins: "*", - AllowMethods: strings.Join([]string{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{ fiber.MethodGet, fiber.MethodPost, fiber.MethodHead, fiber.MethodPut, fiber.MethodDelete, fiber.MethodPatch, - }, ","), - AllowHeaders: "", - AllowCredentials: false, - ExposeHeaders: "", - MaxAge: 0, + }, + AllowHeaders: []string{}, + AllowCredentials: false, + ExposeHeaders: []string{}, + MaxAge: 0, AllowPrivateNetwork: false, } ``` diff --git a/middleware/cors/config.go b/middleware/cors/config.go new file mode 100644 index 0000000000..6e1d4697ba --- /dev/null +++ b/middleware/cors/config.go @@ -0,0 +1,93 @@ +package cors + +import ( + "github.com/gofiber/fiber/v3" +) + +// Config defines the config for middleware. +type Config struct { + // Next defines a function to skip this middleware when returned true. + // + // Optional. Default: nil + Next func(c fiber.Ctx) bool + + // AllowOriginsFunc defines a function that will set the 'Access-Control-Allow-Origin' + // response header to the 'origin' request header when returned true. This allows for + // dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins + // will be not have the 'Access-Control-Allow-Credentials' header set to 'true'. + // + // Optional. Default: nil + AllowOriginsFunc func(origin string) bool + + // AllowOrigin defines a list of origins that may access the resource. + // + // This supports subdomains wildcarding by prefixing the domain with a `*.` + // e.g. "http://.domain.com". This will allow all level of subdomains of domain.com to access the resource. + // + // If the special wildcard `"*"` is present in the list, all origins will be allowed. + // + // Optional. Default value []string{} + AllowOrigins []string + + // AllowMethods defines a list methods allowed when accessing the resource. + // This is used in response to a preflight request. + // + // Optional. Default value []string{"GET", "POST", "HEAD", "PUT", "DELETE", "PATCH"} + AllowMethods []string + + // AllowHeaders defines a list of request headers that can be used when + // making the actual request. This is in response to a preflight request. + // + // Optional. Default value []string{} + AllowHeaders []string + + // AllowCredentials indicates whether or not the response to the request + // can be exposed when the credentials flag is true. When used as part of + // a response to a preflight request, this indicates whether or not the + // actual request can be made using credentials. Note: If true, AllowOrigins + // cannot be set to true to prevent security vulnerabilities. + // + // Optional. Default value false. + AllowCredentials bool + + // ExposeHeaders defines a whitelist headers that clients are allowed to + // access. + // + // Optional. Default value []string{}. + ExposeHeaders []string + + // MaxAge indicates how long (in seconds) the results of a preflight request + // can be cached. + // If you pass MaxAge 0, Access-Control-Max-Age header will not be added and + // browser will use 5 seconds by default. + // To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header 0. + // + // Optional. Default value 0. + MaxAge int + + // AllowPrivateNetwork indicates whether the Access-Control-Allow-Private-Network + // response header should be set to true, allowing requests from private networks. + // + // Optional. Default value false. + AllowPrivateNetwork bool +} + +// ConfigDefault is the default config +var ConfigDefault = Config{ + Next: nil, + AllowOriginsFunc: nil, + AllowOrigins: []string{"*"}, + AllowMethods: []string{ + fiber.MethodGet, + fiber.MethodPost, + fiber.MethodHead, + fiber.MethodPut, + fiber.MethodDelete, + fiber.MethodPatch, + }, + AllowHeaders: []string{}, + AllowCredentials: false, + ExposeHeaders: []string{}, + MaxAge: 0, + AllowPrivateNetwork: false, +} diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index 4fe0eeebb4..e0fac91739 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -8,89 +8,6 @@ import ( "github.com/gofiber/fiber/v3/log" ) -// Config defines the config for middleware. -type Config struct { - // Next defines a function to skip this middleware when returned true. - // - // Optional. Default: nil - Next func(c fiber.Ctx) bool - - // AllowOriginsFunc defines a function that will set the 'Access-Control-Allow-Origin' - // response header to the 'origin' request header when returned true. This allows for - // dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins - // will be not have the 'Access-Control-Allow-Credentials' header set to 'true'. - // - // Optional. Default: nil - AllowOriginsFunc func(origin string) bool - - // AllowOrigin defines a comma separated list of origins that may access the resource. - // - // Optional. Default value "*" - AllowOrigins string - - // AllowMethods defines a list methods allowed when accessing the resource. - // This is used in response to a preflight request. - // - // Optional. Default value "GET,POST,HEAD,PUT,DELETE,PATCH" - AllowMethods string - - // AllowHeaders defines a list of request headers that can be used when - // making the actual request. This is in response to a preflight request. - // - // Optional. Default value "". - AllowHeaders string - - // AllowCredentials indicates whether or not the response to the request - // can be exposed when the credentials flag is true. When used as part of - // a response to a preflight request, this indicates whether or not the - // actual request can be made using credentials. Note: If true, AllowOrigins - // cannot be set to a wildcard ("*") to prevent security vulnerabilities. - // - // Optional. Default value false. - AllowCredentials bool - - // ExposeHeaders defines a whitelist headers that clients are allowed to - // access. - // - // Optional. Default value "". - ExposeHeaders string - - // MaxAge indicates how long (in seconds) the results of a preflight request - // can be cached. - // If you pass MaxAge 0, Access-Control-Max-Age header will not be added and - // browser will use 5 seconds by default. - // To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header 0. - // - // Optional. Default value 0. - MaxAge int - - // AllowPrivateNetwork indicates whether the Access-Control-Allow-Private-Network - // response header should be set to true, allowing requests from private networks. - // - // Optional. Default value false. - AllowPrivateNetwork bool -} - -// ConfigDefault is the default config -var ConfigDefault = Config{ - Next: nil, - AllowOriginsFunc: nil, - AllowOrigins: "*", - AllowMethods: strings.Join([]string{ - fiber.MethodGet, - fiber.MethodPost, - fiber.MethodHead, - fiber.MethodPut, - fiber.MethodDelete, - fiber.MethodPatch, - }, ","), - AllowHeaders: "", - AllowCredentials: false, - ExposeHeaders: "", - MaxAge: 0, - AllowPrivateNetwork: false, -} - // New creates a new middleware handler func New(config ...Config) fiber.Handler { // Set default config @@ -101,25 +18,16 @@ func New(config ...Config) fiber.Handler { cfg = config[0] // Set default values - if cfg.AllowMethods == "" { + if len(cfg.AllowMethods) == 0 { cfg.AllowMethods = ConfigDefault.AllowMethods } - // When none of the AllowOrigins or AllowOriginsFunc config was defined, set the default AllowOrigins value with "*" - if cfg.AllowOrigins == "" && cfg.AllowOriginsFunc == nil { - cfg.AllowOrigins = ConfigDefault.AllowOrigins - } } // Warning logs if both AllowOrigins and AllowOriginsFunc are set - if cfg.AllowOrigins != "" && cfg.AllowOriginsFunc != nil { + if len(cfg.AllowOrigins) > 0 && cfg.AllowOriginsFunc != nil { log.Warn("[CORS] Both 'AllowOrigins' and 'AllowOriginsFunc' have been defined.") } - // Validate CORS credentials configuration - if cfg.AllowCredentials && cfg.AllowOrigins == "*" { - panic("[CORS] Insecure setup, 'AllowCredentials' is set to true, and 'AllowOrigins' is set to a wildcard.") - } - // allowOrigins is a slice of strings that contains the allowed origins // defined in the 'AllowOrigins' configuration. allowOrigins := []string{} @@ -127,34 +35,41 @@ func New(config ...Config) fiber.Handler { allowAllOrigins := false // Validate and normalize static AllowOrigins - if cfg.AllowOrigins != "" && cfg.AllowOrigins != "*" { - origins := strings.Split(cfg.AllowOrigins, ",") - for _, origin := range origins { - if i := strings.Index(origin, "://*."); i != -1 { - trimmedOrigin := strings.TrimSpace(origin[:i+3] + origin[i+4:]) - isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) - if !isValid { - panic("[CORS] Invalid origin format in configuration: " + trimmedOrigin) - } - sd := subdomain{prefix: normalizedOrigin[:i+3], suffix: normalizedOrigin[i+3:]} - allowSOrigins = append(allowSOrigins, sd) - } else { - trimmedOrigin := strings.TrimSpace(origin) - isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) - if !isValid { - panic("[CORS] Invalid origin format in configuration: " + trimmedOrigin) - } - allowOrigins = append(allowOrigins, normalizedOrigin) + if len(cfg.AllowOrigins) == 0 && cfg.AllowOriginsFunc == nil { + allowAllOrigins = true + } + for _, origin := range cfg.AllowOrigins { + if origin == "*" { + allowAllOrigins = true + break + } + if i := strings.Index(origin, "://*."); i != -1 { + trimmedOrigin := strings.TrimSpace(origin[:i+3] + origin[i+4:]) + isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) + if !isValid { + panic("[CORS] Invalid origin format in configuration: " + trimmedOrigin) } + sd := subdomain{prefix: normalizedOrigin[:i+3], suffix: normalizedOrigin[i+3:]} + allowSOrigins = append(allowSOrigins, sd) + } else { + trimmedOrigin := strings.TrimSpace(origin) + isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) + if !isValid { + panic("[CORS] Invalid origin format in configuration: " + trimmedOrigin) + } + allowOrigins = append(allowOrigins, normalizedOrigin) } - } else if cfg.AllowOrigins == "*" { - allowAllOrigins = true } - // Strip white spaces - allowMethods := strings.ReplaceAll(cfg.AllowMethods, " ", "") - allowHeaders := strings.ReplaceAll(cfg.AllowHeaders, " ", "") - exposeHeaders := strings.ReplaceAll(cfg.ExposeHeaders, " ", "") + // Validate CORS credentials configuration + if cfg.AllowCredentials && allowAllOrigins { + panic("[CORS] Configuration error: When 'AllowCredentials' is set to true, 'AllowOrigins' cannot contain a wildcard origin '*'. Please specify allowed origins explicitly or adjust 'AllowCredentials' setting.") + } + + // Warn if allowAllOrigins is set to true and AllowOriginsFunc is defined + if allowAllOrigins && cfg.AllowOriginsFunc != nil { + log.Warn("[CORS] 'AllowOrigins' is set to allow all origins, 'AllowOriginsFunc' will not be used.") + } // Convert int to string maxAge := strconv.Itoa(cfg.MaxAge) @@ -230,7 +145,7 @@ func New(config ...Config) fiber.Handler { // See https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches c.Vary(fiber.HeaderOrigin) } - setCORSHeaders(c, allowOrigin, "", "", exposeHeaders, maxAge, cfg) + setSimpleHeaders(c, allowOrigin, maxAge, cfg) return c.Next() } @@ -248,15 +163,28 @@ func New(config ...Config) fiber.Handler { } c.Vary(fiber.HeaderOrigin) - setCORSHeaders(c, allowOrigin, allowMethods, allowHeaders, exposeHeaders, maxAge, cfg) + setSimpleHeaders(c, allowOrigin, maxAge, cfg) + + // Set Preflight headers + if len(cfg.AllowMethods) > 0 { + c.Set(fiber.HeaderAccessControlAllowMethods, strings.Join(cfg.AllowMethods, ", ")) + } + if len(cfg.AllowHeaders) > 0 { + c.Set(fiber.HeaderAccessControlAllowHeaders, strings.Join(cfg.AllowHeaders, ", ")) + } else { + h := c.Get(fiber.HeaderAccessControlRequestHeaders) + if h != "" { + c.Set(fiber.HeaderAccessControlAllowHeaders, h) + } + } // Send 204 No Content return c.SendStatus(fiber.StatusNoContent) } } -// Function to set CORS headers -func setCORSHeaders(c fiber.Ctx, allowOrigin, allowMethods, allowHeaders, exposeHeaders, maxAge string, cfg Config) { +// Function to set Simple CORS headers +func setSimpleHeaders(c fiber.Ctx, allowOrigin, maxAge string, cfg Config) { if cfg.AllowCredentials { // When AllowCredentials is true, set the Access-Control-Allow-Origin to the specific origin instead of '*' if allowOrigin == "*" { @@ -271,21 +199,6 @@ func setCORSHeaders(c fiber.Ctx, allowOrigin, allowMethods, allowHeaders, expose c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) } - // Set Allow-Methods if not empty - if allowMethods != "" { - c.Set(fiber.HeaderAccessControlAllowMethods, allowMethods) - } - - // Set Allow-Headers if not empty - if allowHeaders != "" { - c.Set(fiber.HeaderAccessControlAllowHeaders, allowHeaders) - } else { - h := c.Get(fiber.HeaderAccessControlRequestHeaders) - if h != "" { - c.Set(fiber.HeaderAccessControlAllowHeaders, h) - } - } - // Set MaxAge if set if cfg.MaxAge > 0 { c.Set(fiber.HeaderAccessControlMaxAge, maxAge) @@ -294,7 +207,7 @@ func setCORSHeaders(c fiber.Ctx, allowOrigin, allowMethods, allowHeaders, expose } // Set Expose-Headers if not empty - if exposeHeaders != "" { - c.Set(fiber.HeaderAccessControlExposeHeaders, exposeHeaders) + if len(cfg.ExposeHeaders) > 0 { + c.Set(fiber.HeaderAccessControlExposeHeaders, strings.Join(cfg.ExposeHeaders, ", ")) } } diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 5bf943e8b6..fc542e948a 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -26,6 +26,31 @@ func Test_CORS_Empty_Config(t *testing.T) { testDefaultOrEmptyConfig(t, app) } +func Test_CORS_WildcardHeaders(t *testing.T) { + t.Parallel() + app := fiber.New() + app.Use(New(Config{ + AllowMethods: []string{"*"}, + AllowHeaders: []string{"*"}, + ExposeHeaders: []string{"*"}, + })) + + h := app.Handler() + + // Test preflight request + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fiber.MethodOptions) + ctx.Request.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodGet) + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://localhost") + h(ctx) + + require.Equal(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) + require.Equal(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods))) + require.Equal(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders))) + require.Equal(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlExposeHeaders))) +} + func Test_CORS_Negative_MaxAge(t *testing.T) { t.Parallel() @@ -63,7 +88,7 @@ func testDefaultOrEmptyConfig(t *testing.T, app *fiber.App) { ctx.Request.Header.Set(fiber.HeaderOrigin, "http://localhost") h(ctx) - require.Equal(t, "GET,POST,HEAD,PUT,DELETE,PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods))) + require.Equal(t, "GET, POST, HEAD, PUT, DELETE, PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods))) require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders))) require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlMaxAge))) } @@ -73,7 +98,7 @@ func Test_CORS_AllowOrigins_Vary(t *testing.T) { app := fiber.New() app.Use(New( Config{ - AllowOrigins: "http://localhost", + AllowOrigins: []string{"http://localhost"}, }, )) @@ -102,10 +127,9 @@ func Test_CORS_Wildcard(t *testing.T) { app := fiber.New() // OPTIONS (preflight) response headers when AllowOrigins is * app.Use(New(Config{ - AllowOrigins: "*", MaxAge: 3600, - ExposeHeaders: "X-Request-ID", - AllowHeaders: "Authentication", + ExposeHeaders: []string{"X-Request-ID"}, + AllowHeaders: []string{"Authentication"}, })) // Get handler pointer handler := app.Handler() @@ -145,11 +169,11 @@ func Test_CORS_Origin_AllowCredentials(t *testing.T) { app := fiber.New() // OPTIONS (preflight) response headers when AllowOrigins is * app.Use(New(Config{ - AllowOrigins: "http://localhost", + AllowOrigins: []string{"http://localhost"}, AllowCredentials: true, MaxAge: 3600, - ExposeHeaders: "X-Request-ID", - AllowHeaders: "Authentication", + ExposeHeaders: []string{"X-Request-ID"}, + AllowHeaders: []string{"Authentication"}, })) // Get handler pointer handler := app.Handler() @@ -196,7 +220,7 @@ func Test_CORS_Wildcard_AllowCredentials_Panic(t *testing.T) { }() app.Use(New(Config{ - AllowOrigins: "*", + AllowOrigins: []string{"*"}, AllowCredentials: true, })) }() @@ -217,6 +241,7 @@ func Test_CORS_Invalid_Origins_Panic(t *testing.T) { "https://*", "http://*.com*", "invalid url", + "*", "http://origin.com,invalid url", // add more invalid origins as needed } @@ -234,7 +259,7 @@ func Test_CORS_Invalid_Origins_Panic(t *testing.T) { }() app.Use(New(Config{ - AllowOrigins: origin, + AllowOrigins: []string{origin}, AllowCredentials: true, })) }() @@ -251,7 +276,9 @@ func Test_CORS_Subdomain(t *testing.T) { // New fiber instance app := fiber.New() // OPTIONS (preflight) response headers when AllowOrigins is set to a subdomain - app.Use("/", New(Config{AllowOrigins: "http://*.example.com"})) + app.Use("/", New(Config{ + AllowOrigins: []string{"http://*.example.com"}, + })) // Get handler pointer handler := app.Handler() @@ -299,106 +326,107 @@ func Test_CORS_Subdomain(t *testing.T) { func Test_CORS_AllowOriginScheme(t *testing.T) { t.Parallel() tests := []struct { - reqOrigin, pattern string - shouldAllowOrigin bool + pattern []string + reqOrigin string + shouldAllowOrigin bool }{ { - pattern: "http://example.com", + pattern: []string{"http://example.com"}, reqOrigin: "http://example.com", shouldAllowOrigin: true, }, { - pattern: "HTTP://EXAMPLE.COM", + pattern: []string{"HTTP://EXAMPLE.COM"}, reqOrigin: "http://example.com", shouldAllowOrigin: true, }, { - pattern: "https://example.com", + pattern: []string{"https://example.com"}, reqOrigin: "https://example.com", shouldAllowOrigin: true, }, { - pattern: "http://example.com", + pattern: []string{"http://example.com"}, reqOrigin: "https://example.com", shouldAllowOrigin: false, }, { - pattern: "http://*.example.com", + pattern: []string{"http://*.example.com"}, reqOrigin: "http://aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://*.example.com", + pattern: []string{"http://*.example.com"}, reqOrigin: "http://bbb.aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://*.aaa.example.com", + pattern: []string{"http://*.aaa.example.com"}, reqOrigin: "http://bbb.aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://*.example.com:8080", + pattern: []string{"http://*.example.com:8080"}, reqOrigin: "http://aaa.example.com:8080", shouldAllowOrigin: true, }, { - pattern: "http://*.example.com", + pattern: []string{"http://*.example.com"}, reqOrigin: "http://1.2.aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://example.com", + pattern: []string{"http://example.com"}, reqOrigin: "http://gofiber.com", shouldAllowOrigin: false, }, { - pattern: "http://*.aaa.example.com", + pattern: []string{"http://*.aaa.example.com"}, reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: false, }, { - pattern: "http://*.example.com", + pattern: []string{"http://*.example.com"}, reqOrigin: "http://1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.example.com", shouldAllowOrigin: true, }, { - pattern: "http://example.com", + pattern: []string{"http://example.com"}, reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: false, }, { - pattern: "https://--aaa.bbb.com", + pattern: []string{"https://--aaa.bbb.com"}, reqOrigin: "https://prod-preview--aaa.bbb.com", shouldAllowOrigin: false, }, { - pattern: "http://*.example.com", + pattern: []string{"http://*.example.com"}, reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: true, }, { - pattern: "http://domain-1.com, http://example.com", + pattern: []string{"http://domain-1.com", "http://example.com"}, reqOrigin: "http://example.com", shouldAllowOrigin: true, }, { - pattern: "http://domain-1.com, http://example.com", + pattern: []string{"http://domain-1.com", "http://example.com"}, reqOrigin: "http://domain-2.com", shouldAllowOrigin: false, }, { - pattern: "http://domain-1.com, http://example.com", + pattern: []string{"http://domain-1.com", "http://example.com"}, reqOrigin: "http://example.com", shouldAllowOrigin: true, }, { - pattern: "http://domain-1.com, http://example.com", + pattern: []string{"http://domain-1.com", "http://example.com"}, reqOrigin: "http://domain-2.com", shouldAllowOrigin: false, }, { - pattern: "http://domain-1.com,http://example.com", + pattern: []string{"http://domain-1.com", "http://example.com"}, reqOrigin: "http://domain-1.com", shouldAllowOrigin: true, }, @@ -431,7 +459,7 @@ func Test_CORS_AllowOriginHeader_NoMatch(t *testing.T) { // New fiber instance app := fiber.New() app.Use("/", New(Config{ - AllowOrigins: "http://example-1.com, https://example-1.com", + AllowOrigins: []string{"http://example-1.com", "https://example-1.com"}, })) // Get handler pointer @@ -474,7 +502,7 @@ func Test_CORS_Next(t *testing.T) { func Test_CORS_Headers_BasedOnRequestType(t *testing.T) { t.Parallel() app := fiber.New() - app.Use(New(Config{})) + app.Use(New()) app.Use(func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) @@ -516,7 +544,7 @@ func Test_CORS_Headers_BasedOnRequestType(t *testing.T) { handler(ctx) require.Equal(t, 204, ctx.Response.StatusCode(), "Status code should be 204") require.Equal(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin)), "Access-Control-Allow-Origin header should be set") - require.Equal(t, "GET,POST,HEAD,PUT,DELETE,PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods)), "Access-Control-Allow-Methods header should be set (preflight request)") + require.Equal(t, "GET, POST, HEAD, PUT, DELETE, PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods)), "Access-Control-Allow-Methods header should be set (preflight request)") require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders)), "Access-Control-Allow-Headers header should be set (preflight request)") } }) @@ -536,6 +564,24 @@ func Test_CORS_Headers_BasedOnRequestType(t *testing.T) { require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders)), "Access-Control-Allow-Headers header should not be set (non-preflight request)") } }) + + t.Run("Preflight with Access-Control-Request-Headers", func(t *testing.T) { + t.Parallel() + // Make preflight request with origin header and with Access-Control-Request-Method + for _, method := range methods { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fiber.MethodOptions) + ctx.Request.SetRequestURI("https://example.com/") + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://example.com") + ctx.Request.Header.Set(fiber.HeaderAccessControlRequestMethod, method) + ctx.Request.Header.Set(fiber.HeaderAccessControlRequestHeaders, "X-Custom-Header") + handler(ctx) + require.Equal(t, 204, ctx.Response.StatusCode(), "Status code should be 204") + require.Equal(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin)), "Access-Control-Allow-Origin header should be set") + require.Equal(t, "GET, POST, HEAD, PUT, DELETE, PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods)), "Access-Control-Allow-Methods header should be set (preflight request)") + require.Equal(t, "X-Custom-Header", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders)), "Access-Control-Allow-Headers header should be set (preflight request)") + } + }) } func Test_CORS_AllowOriginsAndAllowOriginsFunc(t *testing.T) { @@ -543,7 +589,7 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc(t *testing.T) { // New fiber instance app := fiber.New() app.Use("/", New(Config{ - AllowOrigins: "http://example-1.com", + AllowOrigins: []string{"http://example-1.com"}, AllowOriginsFunc: func(origin string) bool { return strings.Contains(origin, "example-2") }, @@ -643,25 +689,25 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/OriginAllowed", Config: Config{ - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, AllowOriginsFunc: nil, }, RequestOrigin: "http://aaa.com", ResponseOrigin: "http://aaa.com", }, { - Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/NoWhitespace/OriginAllowed", + Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/OriginAllowed", Config: Config{ - AllowOrigins: "http://aaa.com,http://bbb.com", + AllowOrigins: []string{"http://aaa.com", "http://bbb.com"}, AllowOriginsFunc: nil, }, RequestOrigin: "http://bbb.com", ResponseOrigin: "http://bbb.com", }, { - Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/NoWhitespace/OriginNotAllowed", + Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/OriginNotAllowed", Config: Config{ - AllowOrigins: "http://aaa.com,http://bbb.com", + AllowOrigins: []string{"http://aaa.com", "http://bbb.com"}, AllowOriginsFunc: nil, }, RequestOrigin: "http://ccc.com", @@ -670,7 +716,7 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/Whitespace/OriginAllowed", Config: Config{ - AllowOrigins: "http://aaa.com, http://bbb.com", + AllowOrigins: []string{" http://aaa.com ", " http://bbb.com "}, AllowOriginsFunc: nil, }, RequestOrigin: "http://aaa.com", @@ -679,7 +725,7 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/OriginNotAllowed", Config: Config{ - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, AllowOriginsFunc: nil, }, RequestOrigin: "http://bbb.com", @@ -688,7 +734,7 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsDefined/AllowOriginsFuncReturnsTrue/OriginAllowed", Config: Config{ - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, AllowOriginsFunc: func(_ string) bool { return true }, @@ -699,7 +745,7 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsDefined/AllowOriginsFuncReturnsTrue/OriginNotAllowed", Config: Config{ - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, AllowOriginsFunc: func(_ string) bool { return true }, @@ -710,7 +756,7 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsDefined/AllowOriginsFuncReturnsFalse/OriginAllowed", Config: Config{ - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, AllowOriginsFunc: func(_ string) bool { return false }, @@ -721,7 +767,7 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsDefined/AllowOriginsFuncReturnsFalse/OriginNotAllowed", Config: Config{ - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, AllowOriginsFunc: func(_ string) bool { return false }, @@ -732,7 +778,6 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsEmpty/AllowOriginsFuncUndefined/OriginAllowed", Config: Config{ - AllowOrigins: "", AllowOriginsFunc: nil, }, RequestOrigin: "http://aaa.com", @@ -741,7 +786,6 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsEmpty/AllowOriginsFuncReturnsTrue/OriginAllowed", Config: Config{ - AllowOrigins: "", AllowOriginsFunc: func(_ string) bool { return true }, @@ -752,7 +796,6 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { { Name: "AllowOriginsEmpty/AllowOriginsFuncReturnsFalse/OriginNotAllowed", Config: Config{ - AllowOrigins: "", AllowOriginsFunc: func(_ string) bool { return false }, @@ -835,7 +878,7 @@ func Test_CORS_AllowCredentials(t *testing.T) { Name: "AllowOriginsDefined", Config: Config{ AllowCredentials: true, - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, }, RequestOrigin: "http://aaa.com", ResponseOrigin: "http://aaa.com", @@ -845,7 +888,7 @@ func Test_CORS_AllowCredentials(t *testing.T) { Name: "AllowOriginsDefined/UnallowedOrigin", Config: Config{ AllowCredentials: true, - AllowOrigins: "http://aaa.com", + AllowOrigins: []string{"http://aaa.com"}, }, RequestOrigin: "http://bbb.com", ResponseOrigin: "", @@ -986,9 +1029,9 @@ func Test_CORS_AllowPrivateNetwork(t *testing.T) { func Benchmark_CORS_NewHandler(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://localhost,http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://localhost", "http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1021,9 +1064,9 @@ func Benchmark_CORS_NewHandler(b *testing.B) { func Benchmark_CORS_NewHandlerParallel(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://localhost,http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://localhost", "http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1059,9 +1102,9 @@ func Benchmark_CORS_NewHandlerParallel(b *testing.B) { func Benchmark_CORS_NewHandlerSingleOrigin(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1094,9 +1137,9 @@ func Benchmark_CORS_NewHandlerSingleOrigin(b *testing.B) { func Benchmark_CORS_NewHandlerSingleOriginParallel(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1132,9 +1175,8 @@ func Benchmark_CORS_NewHandlerSingleOriginParallel(b *testing.B) { func Benchmark_CORS_NewHandlerWildcard(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "*", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: false, MaxAge: 600, }) @@ -1167,9 +1209,8 @@ func Benchmark_CORS_NewHandlerWildcard(b *testing.B) { func Benchmark_CORS_NewHandlerWildcardParallel(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "*", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: false, MaxAge: 600, }) @@ -1205,9 +1246,9 @@ func Benchmark_CORS_NewHandlerWildcardParallel(b *testing.B) { func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://localhost,http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://localhost", "http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1242,9 +1283,9 @@ func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { func Benchmark_CORS_NewHandlerPreflightParallel(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://localhost,http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://localhost", "http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1281,9 +1322,9 @@ func Benchmark_CORS_NewHandlerPreflightParallel(b *testing.B) { func Benchmark_CORS_NewHandlerPreflightSingleOrigin(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1317,9 +1358,9 @@ func Benchmark_CORS_NewHandlerPreflightSingleOrigin(b *testing.B) { func Benchmark_CORS_NewHandlerPreflightSingleOriginParallel(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "http://example.com", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowOrigins: []string{"http://example.com"}, + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: true, MaxAge: 600, }) @@ -1356,9 +1397,8 @@ func Benchmark_CORS_NewHandlerPreflightSingleOriginParallel(b *testing.B) { func Benchmark_CORS_NewHandlerPreflightWildcard(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "*", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: false, MaxAge: 600, }) @@ -1392,9 +1432,8 @@ func Benchmark_CORS_NewHandlerPreflightWildcard(b *testing.B) { func Benchmark_CORS_NewHandlerPreflightWildcardParallel(b *testing.B) { app := fiber.New() c := New(Config{ - AllowOrigins: "*", - AllowMethods: "GET,POST,PUT,DELETE", - AllowHeaders: "Origin,Content-Type,Accept", + AllowMethods: []string{fiber.MethodGet, fiber.MethodPost, fiber.MethodPut, fiber.MethodDelete}, + AllowHeaders: []string{fiber.HeaderOrigin, fiber.HeaderContentType, fiber.HeaderAccept}, AllowCredentials: false, MaxAge: 600, })