Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
11 changes: 10 additions & 1 deletion spec/v2/providers/https.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -261,6 +264,7 @@ describe("onRequest", () => {
});
} finally {
delete process.env.ORIGINS;
delete process.env.FUNCTIONS_CONTROL_API;
clearParams();
}
});
Expand Down Expand Up @@ -351,6 +355,7 @@ describe("onCall", () => {
afterEach(() => {
delete process.env.GCLOUD_PROJECT;
delete process.env.ORIGINS;
delete process.env.FUNCTIONS_CONTROL_API;
clearParams();
});

Expand Down Expand Up @@ -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",
Expand Down
67 changes: 42 additions & 25 deletions src/v2/providers/https.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,42 @@
return !value || auth.token[claim] === value;
};

type ResolvedCorsOrigin = boolean | string | RegExp | Array<boolean | string | RegExp>;

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()));
};
Comment on lines +283 to +288
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When corsOption is an Expression, its value is resolved at runtime within the CORS middleware callback. Since corsOption.value() can throw an error (e.g., if a required parameter is missing or invalid at runtime), it is safer to wrap this call in a try-catch block and pass any error to the callback. This ensures the cors middleware can handle the error gracefully by propagating it to the Express error handler via next(err).

    return (
      _requestOrigin: string | undefined,
      callback: (err: Error | null, origin?: ResolvedCorsOrigin) => void
    ): void => {
      try {
        callback(null, normalizeCorsOrigin(corsOption.value()));
      } catch (err) {
        callback(err instanceof Error ? err : new Error(String(err)));
      }
    };

}

return normalizeCorsOrigin(corsOption);
}

/**
* Handles HTTPS requests.
*/
Expand Down Expand Up @@ -299,7 +335,7 @@
/**
* Handles HTTPS requests.
* @param handler - A function that takes a {@link https.Request} and response object, same signature as an Express app.
* @returns A function that you can export and deploy.

Check warning

Code scanning / CodeQL

Permissive CORS configuration Medium

CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to permissive or user controlled value.
CORS Origin allows broad access due to permissive or user controlled value.
CORS Origin allows broad access due to permissive or user controlled value.
CORS Origin allows broad access due to permissive or user controlled value.
*/
export function onRequest(
handler: (request: Request, response: express.Response) => void | Promise<void>
Expand All @@ -323,18 +359,9 @@

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;
Expand Down Expand Up @@ -434,24 +461,14 @@
opts = optsOrHandler as CallableOptions;
}

let cors: string | boolean | RegExp | Array<string | RegExp> | 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"));

Check warning

Code scanning / CodeQL

Permissive CORS configuration Medium

CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to
permissive or user controlled value
.
CORS Origin allows broad access due to permissive or user controlled value.
CORS Origin allows broad access due to permissive or user controlled value.
CORS Origin allows broad access due to permissive or user controlled value.
CORS Origin allows broad access due to permissive or user controlled value.
CORS Origin allows broad access due to permissive or user controlled value.

// fix the length of handler to make the call to handler consistent
const fixedLen = (req: CallableRequest<T>, resp?: CallableResponse<Stream>) => handler(req, resp);
Expand Down
Loading