From 5d52216d53fe0616ac08d28c53193fe9822df1a2 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Thu, 24 Nov 2022 16:14:47 +0100 Subject: [PATCH] fix: adds cors caching (#2522) * This PR adds a configurable maxAge header to the CORS middleware. This allows the preflight request to be cached so that we can reduce the request load on our end for the frontend clients starting to utilise the frontend api. --- src/lib/__snapshots__/create-config.test.ts.snap | 1 + src/lib/app.ts | 2 +- src/lib/create-config.ts | 5 +++++ src/lib/middleware/cors-origin-middleware.ts | 10 ++++++---- src/lib/routes/proxy-api/index.ts | 2 +- src/lib/types/option.ts | 2 ++ src/test/e2e/api/proxy/proxy.e2e.test.ts | 10 ++++++++++ website/docs/reference/deploy/configuring-unleash.md | 1 + 8 files changed, 27 insertions(+), 6 deletions(-) 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.