From 20505bce8d9d600066d45bd33a168d9f66a52154 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Thu, 14 May 2026 16:42:00 +0100 Subject: [PATCH 1/2] fix(https): defer CORS param resolution until runtime --- spec/v2/providers/https.spec.ts | 11 +++++- src/v2/providers/https.ts | 65 ++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index a62dfea5a..85251ebfc 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -68,6 +68,7 @@ describe("onRequest", () => { afterEach(() => { options.setGlobalOptions({}); delete process.env.GCLOUD_PROJECT; + delete process.env.FUNCTIONS_CONTROL_API; }); it("should return a minimal trigger/endpoint with appropriate values", () => { @@ -232,7 +233,7 @@ describe("onRequest", () => { const origins = defineList("ORIGINS"); try { - process.env.ORIGINS = '["example.com","example2.com"]'; + process.env.FUNCTIONS_CONTROL_API = "true"; const func = https.onRequest( { cors: origins, @@ -241,6 +242,8 @@ describe("onRequest", () => { res.send("42"); } ); + delete process.env.FUNCTIONS_CONTROL_API; + process.env.ORIGINS = '["example.com","example2.com"]'; const req = request({ headers: { referrer: "example.com", @@ -261,6 +264,7 @@ describe("onRequest", () => { }); } finally { delete process.env.ORIGINS; + delete process.env.FUNCTIONS_CONTROL_API; clearParams(); } }); @@ -351,6 +355,7 @@ describe("onCall", () => { afterEach(() => { delete process.env.GCLOUD_PROJECT; delete process.env.ORIGINS; + delete process.env.FUNCTIONS_CONTROL_API; clearParams(); }); @@ -486,7 +491,11 @@ describe("onCall", () => { }); it("should allow cors params", async () => { + delete process.env.ORIGINS; + process.env.FUNCTIONS_CONTROL_API = "true"; const func = https.onCall({ cors: origins }, () => 42); + delete process.env.FUNCTIONS_CONTROL_API; + process.env.ORIGINS = '["example.com","example2.com"]'; const req = request({ headers: { referrer: "example.com", diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index cfb3cfee3..c40201c12 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -255,6 +255,40 @@ export const hasClaim = return !value || auth.token[claim] === value; }; +type ResolvedCorsOrigin = boolean | string | RegExp | Array; + +function normalizeCorsOrigin(origin: ResolvedCorsOrigin | undefined): ResolvedCorsOrigin | undefined { + // Arrays cause the access-control-allow-origin header to be dynamic based + // on the origin header of the request. If there is only one element in the + // array, this is unnecessary. + if (Array.isArray(origin) && origin.length === 1) { + return origin[0]; + } + + return origin; +} + +function resolveCorsOrigin( + corsOption: HttpsOptions["cors"], + isCorsDebugEnabled: boolean +): cors.CorsOptions["origin"] { + if (isCorsDebugEnabled) { + // Respect `cors: false` to turn off cors even if debug feature is enabled. + return corsOption === false ? false : true; + } + + if (corsOption instanceof Expression) { + return ( + _requestOrigin: string | undefined, + callback: (err: Error | null, origin?: ResolvedCorsOrigin) => void + ): void => { + callback(null, normalizeCorsOrigin(corsOption.value())); + }; + } + + return normalizeCorsOrigin(corsOption); +} + /** * Handles HTTPS requests. */ @@ -323,18 +357,9 @@ export function onRequest( handler = withErrorHandler(handler); - if (isDebugFeatureEnabled("enableCors") || "cors" in opts) { - let origin = opts.cors instanceof Expression ? opts.cors.value() : opts.cors; - if (isDebugFeatureEnabled("enableCors")) { - // Respect `cors: false` to turn off cors even if debug feature is enabled. - origin = opts.cors === false ? false : true; - } - // Arrays cause the access-control-allow-origin header to be dynamic based - // on the origin header of the request. If there is only one element in the - // array, this is unnecessary. - if (Array.isArray(origin) && origin.length === 1) { - origin = origin[0]; - } + const isCorsDebugEnabled = isDebugFeatureEnabled("enableCors"); + if (isCorsDebugEnabled || "cors" in opts) { + const origin = resolveCorsOrigin(opts.cors, isCorsDebugEnabled); const middleware = cors({ origin }); const userProvidedHandler = handler; @@ -434,24 +459,14 @@ export function onCall, Stream = unknown>( opts = optsOrHandler as CallableOptions; } - let cors: string | boolean | RegExp | Array | undefined; + let cors: HttpsOptions["cors"]; if ("cors" in opts) { - if (opts.cors instanceof Expression) { - cors = opts.cors.value(); - } else { - cors = opts.cors; - } + cors = opts.cors; } else { cors = true; } - let origin = isDebugFeatureEnabled("enableCors") ? true : cors; - // Arrays cause the access-control-allow-origin header to be dynamic based - // on the origin header of the request. If there is only one element in the - // array, this is unnecessary. - if (Array.isArray(origin) && origin.length === 1) { - origin = origin[0]; - } + const origin = resolveCorsOrigin(cors, isDebugFeatureEnabled("enableCors")); // fix the length of handler to make the call to handler consistent const fixedLen = (req: CallableRequest, resp?: CallableResponse) => handler(req, resp); From cea7de3479f1bdbcadcad908c6a466c09b7500f7 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Thu, 14 May 2026 16:59:27 +0100 Subject: [PATCH 2/2] refactor(https): improve formatting of normalizeCorsOrigin function for better readability --- src/v2/providers/https.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index c40201c12..17c4e3c67 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -257,7 +257,9 @@ export const hasClaim = type ResolvedCorsOrigin = boolean | string | RegExp | Array; -function normalizeCorsOrigin(origin: ResolvedCorsOrigin | undefined): ResolvedCorsOrigin | undefined { +function normalizeCorsOrigin( + origin: ResolvedCorsOrigin | undefined +): ResolvedCorsOrigin | undefined { // Arrays cause the access-control-allow-origin header to be dynamic based // on the origin header of the request. If there is only one element in the // array, this is unnecessary.