From 193b973157f8aaa91a025a044a45ebcef6cdb8fe Mon Sep 17 00:00:00 2001 From: Izaak Gough Date: Tue, 7 Jul 2026 17:12:45 +0100 Subject: [PATCH 1/2] fix: remove false warning from enforceAppCheck and consumeAppCheckToken --- src/v2/providers/https.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 8aa9438ec..ebf82d8ad 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -439,12 +439,12 @@ export function onCall, Stream = unknown>( let enforceAppCheck = opts.enforceAppCheck ?? options.getGlobalOptions().enforceAppCheck; if (enforceAppCheck instanceof Expression) { - enforceAppCheck = enforceAppCheck.value(); + enforceAppCheck = enforceAppCheck.runtimeValue(); } let consumeAppCheckToken = opts.consumeAppCheckToken; if (consumeAppCheckToken instanceof Expression) { - consumeAppCheckToken = consumeAppCheckToken.value(); + consumeAppCheckToken = consumeAppCheckToken.runtimeValue(); } let func: any = onCallHandler( From c891be700749cbbbe18f8a1a78015f59b0664768 Mon Sep 17 00:00:00 2001 From: Izaak Gough Date: Wed, 29 Jul 2026 10:48:21 +0100 Subject: [PATCH 2/2] refactor: defer enforceAppCheck/consumeAppCheckToken expression resolution to request time --- spec/v2/providers/https.spec.ts | 23 +++++++++++++++++++++++ src/common/providers/https.ts | 13 +++++++------ src/v2/providers/https.ts | 11 ++--------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index 85930c36c..d02e74dfb 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -615,6 +615,29 @@ describe("onCall", () => { } }); + it("should not warn when using Expression-based enforceAppCheck during deployment", async () => { + const loggerSpy = sinon.spy(logger, "warn"); + const enforceAppCheck = defineBoolean("ENFORCE_APP_CHECK"); + + try { + process.env.ENFORCE_APP_CHECK = "true"; + process.env.FUNCTIONS_CONTROL_API = "true"; + + const func = https.onCall({ enforceAppCheck }, () => 42); + + const req = request({ headers: { origin: "example.com" } }); // No app check token + const resp = await runHandler(func, req); + + expect(resp.status).to.equal(401); + expect(loggerSpy.called).to.be.false; + } finally { + delete process.env.ENFORCE_APP_CHECK; + delete process.env.FUNCTIONS_CONTROL_API; + clearParams(); + loggerSpy.restore(); + } + }); + it("overrides CORS headers if debug feature is enabled", async () => { sinon.stub(debug, "isDebugFeatureEnabled").withArgs("enableCors").returns(true); diff --git a/src/common/providers/https.ts b/src/common/providers/https.ts index c636067a7..1ce1b1773 100644 --- a/src/common/providers/https.ts +++ b/src/common/providers/https.ts @@ -34,6 +34,7 @@ import { getApp } from "../app"; import { isDebugFeatureEnabled } from "../debug"; import { TaskContext } from "./tasks"; import { Expression } from "../../params"; +import { valueOf } from "../../params/types"; const JWT_REGEX = /^[a-zA-Z0-9\-_=]+?\.[a-zA-Z0-9\-_=]+?\.([a-zA-Z0-9\-_=]+)?$/; @@ -675,12 +676,12 @@ async function checkAppCheckToken( if (isDebugFeatureEnabled("skipTokenVerification")) { const decodedToken = unsafeDecodeAppCheckToken(appCheckToken); appCheckData = { appId: decodedToken.app_id, token: decodedToken }; - if (options.consumeAppCheckToken) { + if (valueOf(options.consumeAppCheckToken)) { appCheckData.alreadyConsumed = false; } } else { const appCheck = getAppCheck(getApp()); - if (options.consumeAppCheckToken) { + if (valueOf(options.consumeAppCheckToken)) { if (appCheck.verifyToken?.length === 1) { const errorMsg = "Unsupported version of the Admin SDK." + @@ -768,8 +769,8 @@ export type CorsInfo = Omit & { /** @internal **/ export interface CallableOptions { cors: CorsInfo; - enforceAppCheck?: boolean; - consumeAppCheckToken?: boolean; + enforceAppCheck?: boolean | Expression; + consumeAppCheckToken?: boolean | Expression; /* @deprecated */ authPolicy?: (token: AuthData | null, data: T) => boolean | Promise; /** @@ -882,7 +883,7 @@ function wrapOnCallHandler( throw new HttpsError("unauthenticated", "Unauthenticated"); } if (tokenStatus.app === "INVALID") { - if (options.enforceAppCheck) { + if (valueOf(options.enforceAppCheck)) { throw new HttpsError("unauthenticated", "Unauthenticated"); } else { logger.warn( @@ -890,7 +891,7 @@ function wrapOnCallHandler( ); } } - if (tokenStatus.app === "MISSING" && options.enforceAppCheck) { + if (tokenStatus.app === "MISSING" && valueOf(options.enforceAppCheck)) { throw new HttpsError("unauthenticated", "Unauthenticated"); } diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index ebf82d8ad..f24ef8335 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -437,15 +437,8 @@ export function onCall, Stream = unknown>( // fix the length of handler to make the call to handler consistent const fixedLen = (req: CallableRequest, resp?: CallableResponse) => handler(req, resp); - let enforceAppCheck = opts.enforceAppCheck ?? options.getGlobalOptions().enforceAppCheck; - if (enforceAppCheck instanceof Expression) { - enforceAppCheck = enforceAppCheck.runtimeValue(); - } - - let consumeAppCheckToken = opts.consumeAppCheckToken; - if (consumeAppCheckToken instanceof Expression) { - consumeAppCheckToken = consumeAppCheckToken.runtimeValue(); - } + const enforceAppCheck = opts.enforceAppCheck ?? options.getGlobalOptions().enforceAppCheck; + const consumeAppCheckToken = opts.consumeAppCheckToken; let func: any = onCallHandler( {