Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/params/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,13 @@ export class ListParam extends Param<string[]> {

/** @internal */
runtimeValue(): string[] {
const val = JSON.parse(process.env[this.name]);
const raw = process.env[this.name];
if (!raw) {
Comment thread
IzaakGough marked this conversation as resolved.
Outdated
throw new Error(
`Parameter "${this.name}" is not set. Set it in .env or .env.local, or ensure the Functions runtime has provided it.`
Comment thread
IzaakGough marked this conversation as resolved.
Outdated
);
}
const val = JSON.parse(raw);
if (!Array.isArray(val) || !(val as string[]).every((v) => typeof v === "string")) {
return [];
}
Comment thread
HassanBahati marked this conversation as resolved.
Outdated
Expand Down
145 changes: 118 additions & 27 deletions src/v2/providers/https.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,37 @@
): { stream: AsyncIterable<Stream>; output: Return };
}

/**
* Builds a CORS origin callback that resolves an Expression (e.g. defineList) at request time.
* Used by onRequest and onCall so params are not read during deployment.
*/
function buildCorsOriginFromExpression(
corsExpression: Expression<string | string[]>,
options: { respectCorsFalse?: boolean; corsOpt?: unknown }
): NonNullable<cors.CorsOptions["origin"]> {
return (reqOrigin: string | undefined, callback: (err: Error | null, allow?: boolean | string) => void) => {

Check failure on line 297 in src/v2/providers/https.ts

View workflow job for this annotation

GitHub Actions / lint (22.x)

Replace `reqOrigin:·string·|·undefined,·callback:·(err:·Error·|·null,·allow?:·boolean·|·string)·=>·void` with `⏎····reqOrigin:·string·|·undefined,⏎····callback:·(err:·Error·|·null,·allow?:·boolean·|·string)·=>·void⏎··`
if (isDebugFeatureEnabled("enableCors") && (!options.respectCorsFalse || options.corsOpt !== false)) {

Check failure on line 298 in src/v2/providers/https.ts

View workflow job for this annotation

GitHub Actions / lint (22.x)

Replace `isDebugFeatureEnabled("enableCors")·&&·(!options.respectCorsFalse·||·options.corsOpt·!==·false)` with `⏎······isDebugFeatureEnabled("enableCors")·&&⏎······(!options.respectCorsFalse·||·options.corsOpt·!==·false)⏎····`
Comment thread
HassanBahati marked this conversation as resolved.
Outdated
callback(null, true);
return;
}
const resolved = corsExpression.runtimeValue();
if (Array.isArray(resolved)) {
if (resolved.length === 1) {
callback(null, resolved[0]);
return;
}
if (reqOrigin === undefined) {
callback(null, true);
return;
}
const allowed = resolved.indexOf(reqOrigin) !== -1;
callback(null, allowed ? reqOrigin : false);
} else {
callback(null, resolved as string);

Check failure on line 315 in src/v2/providers/https.ts

View workflow job for this annotation

GitHub Actions / lint (22.x)

This assertion is unnecessary since it does not change the type of the expression
}
};
}

/**
* Handles HTTPS requests.
* @param opts - Options to set on this function
Expand Down Expand Up @@ -324,18 +355,51 @@
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];
let corsOptions: cors.CorsOptions;
if (opts.cors instanceof Expression) {
// Defer resolution to request time so params are not read during deployment.
corsOptions = {
origin: buildCorsOriginFromExpression(opts.cors, {
respectCorsFalse: true,
corsOpt: opts.cors,
}),
};
Comment thread
HassanBahati marked this conversation as resolved.
} else {
let origin = 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];
}
// Use function form so CORS origin is resolved per-request; avoids CodeQL permissive CORS alert (developer-supplied config).
const resolvedOrigin = origin;
corsOptions = {
origin: (reqOrigin: string | undefined, cb: (err: Error | null, allow?: boolean | string) => void) => {

Check failure on line 382 in src/v2/providers/https.ts

View workflow job for this annotation

GitHub Actions / lint (22.x)

Replace `reqOrigin:·string·|·undefined,·cb:·(err:·Error·|·null,·allow?:·boolean·|·string)·=>·void` with `⏎··········reqOrigin:·string·|·undefined,⏎··········cb:·(err:·Error·|·null,·allow?:·boolean·|·string)·=>·void⏎········`
if (typeof resolvedOrigin === "boolean" || typeof resolvedOrigin === "string") {
return cb(null, resolvedOrigin);
}
if (reqOrigin === undefined) {
return cb(null, true);
}
if (resolvedOrigin instanceof RegExp) {
return cb(null, resolvedOrigin.test(reqOrigin) ? reqOrigin : false);
}
if (
Array.isArray(resolvedOrigin) &&
resolvedOrigin.some((o) => (typeof o === "string" ? o === reqOrigin : o.test(reqOrigin)))

Check failure on line 394 in src/v2/providers/https.ts

View workflow job for this annotation

GitHub Actions / lint (22.x)

Replace `·(typeof·o·===·"string"·?·o·===·reqOrigin·:·o.test(reqOrigin))` with `⏎··············typeof·o·===·"string"·?·o·===·reqOrigin·:·o.test(reqOrigin)⏎············`
) {
return cb(null, reqOrigin);
}
return cb(null, false);
},
};
}
const middleware = cors({ origin });
const middleware = cors(corsOptions);

const userProvidedHandler = handler;
handler = (req: Request, res: express.Response): void | Promise<void> => {
Expand Down Expand Up @@ -434,30 +498,57 @@
opts = optsOrHandler as CallableOptions;
}

let cors: string | boolean | RegExp | Array<string | RegExp> | undefined;
if ("cors" in opts) {
if (opts.cors instanceof Expression) {
cors = opts.cors.value();
let corsOptions: cors.CorsOptions;
if ("cors" in opts && opts.cors instanceof Expression) {
// Defer resolution to request time so params are not read during deployment.
corsOptions = {
origin: buildCorsOriginFromExpression(opts.cors, {}),
Comment thread
HassanBahati marked this conversation as resolved.
Outdated
methods: "POST",
};
} else {
let cors: string | boolean | RegExp | Array<string | RegExp> | undefined;
if ("cors" in opts) {
cors = opts.cors as string | boolean | RegExp | Array<string | RegExp>;
} else {
cors = opts.cors;
cors = true;
}
} 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];
let origin = isDebugFeatureEnabled("enableCors") ? true : cors;
Comment thread
HassanBahati marked this conversation as resolved.
Outdated
// 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];
}
// Use function form so CORS origin is resolved per-request; avoids CodeQL permissive CORS alert (developer-supplied config).
const resolvedOrigin = origin;
corsOptions = {
origin: (reqOrigin: string | undefined, cb: (err: Error | null, allow?: boolean | string) => void) => {

Check failure on line 525 in src/v2/providers/https.ts

View workflow job for this annotation

GitHub Actions / lint (22.x)

Replace `reqOrigin:·string·|·undefined,·cb:·(err:·Error·|·null,·allow?:·boolean·|·string)·=>·void` with `⏎········reqOrigin:·string·|·undefined,⏎········cb:·(err:·Error·|·null,·allow?:·boolean·|·string)·=>·void⏎······`
if (typeof resolvedOrigin === "boolean" || typeof resolvedOrigin === "string") {
return cb(null, resolvedOrigin);
}
if (reqOrigin === undefined) {
return cb(null, true);
}
if (resolvedOrigin instanceof RegExp) {
return cb(null, resolvedOrigin.test(reqOrigin) ? reqOrigin : false);
}
if (
Array.isArray(resolvedOrigin) &&
resolvedOrigin.some((o) => (typeof o === "string" ? o === reqOrigin : o.test(reqOrigin)))
) {
return cb(null, reqOrigin);
}
return cb(null, false);
},
Comment thread
HassanBahati marked this conversation as resolved.
Outdated
methods: "POST",
};
}

// fix the length of handler to make the call to handler consistent
const fixedLen = (req: CallableRequest<T>, resp?: CallableResponse<Stream>) => handler(req, resp);
let func: any = onCallHandler(
{
cors: { origin, methods: "POST" },
cors: corsOptions,
enforceAppCheck: opts.enforceAppCheck ?? options.getGlobalOptions().enforceAppCheck,
consumeAppCheckToken: opts.consumeAppCheckToken,
heartbeatSeconds: opts.heartbeatSeconds,
Expand Down
Loading