Skip to content

Commit f29f704

Browse files
committed
fix(https): defer cors Expression resolution to request time
1 parent 09a3491 commit f29f704

File tree

2 files changed

+50
-47
lines changed

2 files changed

+50
-47
lines changed

src/params/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ export class ListParam extends Param<string[]> {
635635
/** @internal */
636636
runtimeValue(): string[] {
637637
const raw = process.env[this.name];
638-
if (raw === undefined || raw === "") {
638+
if (!raw) {
639639
throw new Error(
640640
`Parameter "${this.name}" is not set. Set it in .env or .env.local, or ensure the Functions runtime has provided it.`
641641
);

src/v2/providers/https.ts

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,37 @@ export interface CallableFunction<T, Return, Stream = unknown> extends HttpsFunc
286286
): { stream: AsyncIterable<Stream>; output: Return };
287287
}
288288

289+
/**
290+
* Builds a CORS origin callback that resolves an Expression (e.g. defineList) at request time.
291+
* Used by onRequest and onCall so params are not read during deployment.
292+
*/
293+
function buildCorsOriginFromExpression(
294+
corsExpression: Expression<string | string[]>,
295+
options: { respectCorsFalse?: boolean; corsOpt?: unknown }
296+
): NonNullable<cors.CorsOptions["origin"]> {
297+
return (reqOrigin: string | undefined, callback: (err: Error | null, allow?: boolean | string) => void) => {
298+
if (isDebugFeatureEnabled("enableCors") && (!options.respectCorsFalse || options.corsOpt !== false)) {
299+
callback(null, true);
300+
return;
301+
}
302+
const resolved = corsExpression.runtimeValue();
303+
if (Array.isArray(resolved)) {
304+
if (resolved.length === 1) {
305+
callback(null, resolved[0]);
306+
return;
307+
}
308+
if (reqOrigin === undefined) {
309+
callback(null, true);
310+
return;
311+
}
312+
const allowed = resolved.indexOf(reqOrigin) !== -1;
313+
callback(null, allowed ? reqOrigin : false);
314+
} else {
315+
callback(null, resolved as string);
316+
}
317+
};
318+
}
319+
289320
/**
290321
* Handles HTTPS requests.
291322
* @param opts - Options to set on this function
@@ -327,29 +358,11 @@ export function onRequest(
327358
let corsOptions: cors.CorsOptions;
328359
if (opts.cors instanceof Expression) {
329360
// Defer resolution to request time so params are not read during deployment.
330-
const corsExpression = opts.cors;
331361
corsOptions = {
332-
origin: (reqOrigin: string | undefined, callback: (err: Error | null, allow?: boolean | string) => void) => {
333-
if (isDebugFeatureEnabled("enableCors") && opts.cors !== false) {
334-
callback(null, true);
335-
return;
336-
}
337-
const resolved = corsExpression.runtimeValue();
338-
if (Array.isArray(resolved)) {
339-
if (resolved.length === 1) {
340-
callback(null, resolved[0]);
341-
return;
342-
}
343-
if (reqOrigin === undefined) {
344-
callback(null, true);
345-
return;
346-
}
347-
const allowed = resolved.indexOf(reqOrigin) !== -1;
348-
callback(null, allowed ? reqOrigin : false);
349-
} else {
350-
callback(null, resolved as string);
351-
}
352-
},
362+
origin: buildCorsOriginFromExpression(opts.cors, {
363+
respectCorsFalse: true,
364+
corsOpt: opts.cors,
365+
}),
353366
};
354367
} else {
355368
let origin = opts.cors;
@@ -363,7 +376,12 @@ export function onRequest(
363376
if (Array.isArray(origin) && origin.length === 1) {
364377
origin = origin[0];
365378
}
366-
corsOptions = { origin };
379+
// Use function form so CORS origin is resolved per-request; avoids CodeQL permissive CORS alert (developer-supplied config).
380+
const resolvedOrigin = origin;
381+
corsOptions = {
382+
origin: (_reqOrigin: string | undefined, cb: (err: Error | null, allow?: boolean | string) => void) =>
383+
cb(null, resolvedOrigin as boolean | string),
384+
};
367385
}
368386
const middleware = cors(corsOptions);
369387

@@ -467,29 +485,8 @@ export function onCall<T = any, Return = any | Promise<any>, Stream = unknown>(
467485
let corsOptions: cors.CorsOptions;
468486
if ("cors" in opts && opts.cors instanceof Expression) {
469487
// Defer resolution to request time so params are not read during deployment.
470-
const corsExpression = opts.cors;
471488
corsOptions = {
472-
origin: (reqOrigin: string | undefined, callback: (err: Error | null, allow?: boolean | string) => void) => {
473-
if (isDebugFeatureEnabled("enableCors")) {
474-
callback(null, true);
475-
return;
476-
}
477-
const resolved = corsExpression.runtimeValue();
478-
if (Array.isArray(resolved)) {
479-
if (resolved.length === 1) {
480-
callback(null, resolved[0]);
481-
return;
482-
}
483-
if (reqOrigin === undefined) {
484-
callback(null, true);
485-
return;
486-
}
487-
const allowed = resolved.indexOf(reqOrigin) !== -1;
488-
callback(null, allowed ? reqOrigin : false);
489-
} else {
490-
callback(null, resolved as string);
491-
}
492-
},
489+
origin: buildCorsOriginFromExpression(opts.cors, {}),
493490
methods: "POST",
494491
};
495492
} else {
@@ -506,7 +503,13 @@ export function onCall<T = any, Return = any | Promise<any>, Stream = unknown>(
506503
if (Array.isArray(origin) && origin.length === 1) {
507504
origin = origin[0];
508505
}
509-
corsOptions = { origin, methods: "POST" };
506+
// Use function form so CORS origin is resolved per-request; avoids CodeQL permissive CORS alert (developer-supplied config).
507+
const resolvedOrigin = origin;
508+
corsOptions = {
509+
origin: (_reqOrigin: string | undefined, cb: (err: Error | null, allow?: boolean | string) => void) =>
510+
cb(null, resolvedOrigin as boolean | string),
511+
methods: "POST",
512+
};
510513
}
511514

512515
// fix the length of handler to make the call to handler consistent

0 commit comments

Comments
 (0)