diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 541f7527cb..6303636670 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -2,6 +2,7 @@ exports[`should create default config 1`] = ` { + "accessControlMaxAge": 172800, "additionalCspAllowedDomains": { "defaultSrc": [], "fontSrc": [], diff --git a/src/lib/app.ts b/src/lib/app.ts index 68b5d3b718..0c39f246f9 100644 --- a/src/lib/app.ts +++ b/src/lib/app.ts @@ -80,7 +80,7 @@ export default async function getApp( `${baseUriPath}/api/frontend*`, conditionalMiddleware( () => config.flagResolver.isEnabled('embedProxy'), - corsOriginMiddleware(services), + corsOriginMiddleware(services, config), ), ); diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 48fd9f242f..5432bf50e9 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -450,6 +450,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { DEFAULT_STRATEGY_SEGMENTS_LIMIT, ); + const accessControlMaxAge = options.accessControlMaxAge + ? options.accessControlMaxAge + : parseEnvVarNumber(process.env.ACCESS_CONTROL_MAX_AGE, 172800); + const clientFeatureCaching = loadClientCachingOptions(options); return { @@ -481,6 +485,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { segmentValuesLimit, strategySegmentsLimit, clientFeatureCaching, + accessControlMaxAge, }; } diff --git a/src/lib/middleware/cors-origin-middleware.ts b/src/lib/middleware/cors-origin-middleware.ts index d9b952b823..72d6a6aa8c 100644 --- a/src/lib/middleware/cors-origin-middleware.ts +++ b/src/lib/middleware/cors-origin-middleware.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import cors from 'cors'; -import { IUnleashServices } from '../types'; +import { IUnleashConfig, IUnleashServices } from '../types'; export const allowRequestOrigin = ( requestOrigin: string, @@ -13,9 +13,10 @@ export const allowRequestOrigin = ( // Check the request's Origin header against a list of allowed origins. // The list may include '*', which `cors` does not support natively. -export const corsOriginMiddleware = ({ - settingService, -}: Pick): RequestHandler => { +export const corsOriginMiddleware = ( + { settingService }: Pick, + config: IUnleashConfig, +): RequestHandler => { return cors(async (req, callback) => { try { const { frontendApiOrigins = [] } = @@ -25,6 +26,7 @@ export const corsOriginMiddleware = ({ req.header('Origin'), frontendApiOrigins, ), + maxAge: config.accessControlMaxAge, }); } catch (error) { callback(error); diff --git a/src/lib/routes/proxy-api/index.ts b/src/lib/routes/proxy-api/index.ts index 5c0d8f0313..30bbf366d4 100644 --- a/src/lib/routes/proxy-api/index.ts +++ b/src/lib/routes/proxy-api/index.ts @@ -54,7 +54,7 @@ export default class ProxyController extends Controller { // Support CORS requests for the frontend endpoints. // Preflight requests are handled in `app.ts`. - this.app.use(corsOriginMiddleware(services)); + this.app.use(corsOriginMiddleware(services, config)); this.route({ method: 'get', diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index e4804e6ff8..5fae61338b 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -117,6 +117,7 @@ export interface IUnleashOptions { inlineSegmentConstraints?: boolean; clientFeatureCaching?: Partial; flagResolver?: IFlagResolver; + accessControlMaxAge?: number; } export interface IEmailOption { @@ -202,4 +203,5 @@ export interface IUnleashConfig { segmentValuesLimit: number; strategySegmentsLimit: number; clientFeatureCaching: IClientCachingOption; + accessControlMaxAge: number; } diff --git a/src/test/e2e/api/proxy/proxy.e2e.test.ts b/src/test/e2e/api/proxy/proxy.e2e.test.ts index de2c5250bd..0ad7c65a40 100644 --- a/src/test/e2e/api/proxy/proxy.e2e.test.ts +++ b/src/test/e2e/api/proxy/proxy.e2e.test.ts @@ -981,3 +981,13 @@ test('should return all features when specified', async () => { }); }); }); + +test.only('should return maxAge header on options call', async () => { + await app.request + .options('/api/frontend') + .set('Origin', 'https://example.com') + .expect(204) + .expect((res) => { + expect(res.headers['access-control-max-age']).toBe('172800'); + }); +}); diff --git a/website/docs/reference/deploy/configuring-unleash.md b/website/docs/reference/deploy/configuring-unleash.md index 4c3142378c..a960460bd9 100644 --- a/website/docs/reference/deploy/configuring-unleash.md +++ b/website/docs/reference/deploy/configuring-unleash.md @@ -130,6 +130,7 @@ unleash.start(unleashOptions); - `maxAge` - the time to cache features, set to 600 milliseconds by default - Overridable with (`CLIENT_FEATURE_CACHING_MAXAGE`) ) (accepts milliseconds) - **frontendApi** - Configuration options for the [Unleash front-end API](../front-end-api.md). - `refreshIntervalInMs` - how often (in milliseconds) front-end clients should refresh their data from the cache. Overridable with the `FRONTEND_API_REFRESH_INTERVAL_MS` environment variable. +- **accessControlMaxAge** - You can configure the max-age of the Access-Control-Max-Age header. Defaults to 172800 seconds. Overridable with the `ACCESS_CONTROL_MAX_AGE` environment variable. You can also set the environment variable `ENABLED_ENVIRONMENTS` to a comma delimited string of environment names to override environments.