-
Notifications
You must be signed in to change notification settings - Fork 221
Fix params.defineList cors parsed as undefined at deploy #1813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
09a3491
f29f704
dee756c
f8c5e69
6b93d09
18d8bad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -634,11 +634,26 @@ | |
|
|
||
| /** @internal */ | ||
| runtimeValue(): string[] { | ||
| const val = JSON.parse(process.env[this.name]); | ||
| if (!Array.isArray(val) || !(val as string[]).every((v) => typeof v === "string")) { | ||
| return []; | ||
| const raw = process.env[this.name]; | ||
| if (!raw) { | ||
| throw new Error( | ||
| `Parameter "${this.name}" is not set. Set it in .env or .env.local, or ensure the Functions runtime has provided it.` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| ); | ||
| } | ||
| let val: unknown; | ||
| try { | ||
| val = JSON.parse(raw); | ||
| } catch (error) { | ||
| throw new Error( | ||
| `Parameter "${this.name}" could not be parsed as JSON. Value is: ${raw}. Details: ${error}` | ||
| ); | ||
| } | ||
| if (!Array.isArray(val) || !val.every((v) => typeof v === "string")) { | ||
| throw new Error( | ||
| `Parameter "${this.name}" is not a valid JSON array of strings. Value is: ${raw}` | ||
| ); | ||
| } | ||
| return val as string[]; | ||
| } | ||
|
|
||
| /** @hidden */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -286,6 +286,70 @@ | |
| ): { stream: AsyncIterable<Stream>; output: Return }; | ||
| } | ||
|
|
||
| /** | ||
| * Builds a CORS origin callback for a static value (boolean, string, RegExp, or array). | ||
| * Used by onRequest and onCall for non-Expression cors; function form avoids CodeQL permissive CORS alert. | ||
| */ | ||
| function buildStaticCorsOriginCallback( | ||
| origin: string | boolean | RegExp | Array<string | RegExp> | ||
| ): NonNullable<cors.CorsOptions["origin"]> { | ||
| return (reqOrigin: string | undefined, cb: (err: Error | null, allow?: boolean | string) => void) => { | ||
|
Check failure on line 296 in src/v2/providers/https.ts
|
||
| if (typeof origin === "boolean" || typeof origin === "string") { | ||
| return cb(null, origin); | ||
| } | ||
| if (reqOrigin === undefined) { | ||
| return cb(null, true); | ||
| } | ||
| if (origin instanceof RegExp) { | ||
| return cb(null, origin.test(reqOrigin) ? reqOrigin : false); | ||
| } | ||
| if ( | ||
| Array.isArray(origin) && | ||
| origin.some((o) => (typeof o === "string" ? o === reqOrigin : o.test(reqOrigin))) | ||
| ) { | ||
| return cb(null, reqOrigin); | ||
| } | ||
| return cb(null, false); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * 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 324 in src/v2/providers/https.ts
|
||
| if (isDebugFeatureEnabled("enableCors") && (!options.respectCorsFalse || options.corsOpt !== false)) { | ||
|
Check failure on line 325 in src/v2/providers/https.ts
|
||
HassanBahati marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| callback(null, true); | ||
| return; | ||
| } | ||
| let resolved: string | string[]; | ||
| try { | ||
| resolved = corsExpression.runtimeValue(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you wrote this I added a helper valOf that you can use if you want |
||
| } catch (err) { | ||
| callback(err instanceof Error ? err : new Error(String(err)), false); | ||
| return; | ||
| } | ||
| 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); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Handles HTTPS requests. | ||
| * @param opts - Options to set on this function | ||
|
|
@@ -324,18 +388,32 @@ | |
| 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, | ||
| }), | ||
| }; | ||
| } 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]; | ||
| } | ||
| corsOptions = { | ||
| origin: buildStaticCorsOriginCallback(origin), | ||
| }; | ||
| } | ||
| const middleware = cors({ origin }); | ||
| const middleware = cors(corsOptions); | ||
|
|
||
| const userProvidedHandler = handler; | ||
| handler = (req: Request, res: express.Response): void | Promise<void> => { | ||
|
|
@@ -434,30 +512,45 @@ | |
| 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, { | ||
| respectCorsFalse: true, | ||
| corsOpt: opts.cors, | ||
| }), | ||
| 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 = 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]; | ||
| } | ||
| corsOptions = { | ||
| origin: buildStaticCorsOriginCallback(origin), | ||
| 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, | ||
|
|
||
There was a problem hiding this comment.
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