Skip to content
Draft
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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to return an empty list if the environment variable is missing? I might be steering into the idea of params with default values soon

throw new Error(
`Parameter "${this.name}" is not set. Set it in .env or .env.local, or ensure the Functions runtime has provided it.`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also have .env.project or .env.alias. Maybe we should just say "a dotenv file" and point to documentation?

);
}
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