firebase-functions 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,8 +12,25 @@ export interface Request extends express.Request {
12
12
  * The interface for AppCheck tokens verified in Callable functions
13
13
  */
14
14
  export interface AppCheckData {
15
+ /**
16
+ * The app ID of a Firebase App attested by the App Check token.
17
+ */
15
18
  appId: string;
19
+ /**
20
+ * Decoded App Check token.
21
+ */
16
22
  token: DecodedAppCheckToken;
23
+ /**
24
+ * Indicates if the token has been consumed.
25
+ *
26
+ * @remarks
27
+ * `false` value indicates that this is the first time the App Check service has seen this token and marked the
28
+ * token as consumed for future use of the token.
29
+ *
30
+ * `true` value indicates the token has previously been marked as consumed by the App Check service. In this case,
31
+ * consider taking extra precautions, such as rejecting the request or requiring additional security checks.
32
+ */
33
+ alreadyConsumed?: boolean;
17
34
  }
18
35
  /**
19
36
  * The interface for Auth tokens verified in Callable functions
@@ -276,7 +276,7 @@ exports.unsafeDecodeAppCheckToken = unsafeDecodeAppCheckToken;
276
276
  * @returns {CallableTokenStatus} Status of the token verifications.
277
277
  */
278
278
  /** @internal */
279
- async function checkTokens(req, ctx) {
279
+ async function checkTokens(req, ctx, options) {
280
280
  const verifications = {
281
281
  app: "INVALID",
282
282
  auth: "INVALID",
@@ -286,7 +286,7 @@ async function checkTokens(req, ctx) {
286
286
  verifications.auth = await checkAuthToken(req, ctx);
287
287
  }),
288
288
  Promise.resolve().then(async () => {
289
- verifications.app = await checkAppCheckToken(req, ctx);
289
+ verifications.app = await checkAppCheckToken(req, ctx, options);
290
290
  }),
291
291
  ]);
292
292
  const logPayload = {
@@ -342,25 +342,45 @@ async function checkAuthToken(req, ctx) {
342
342
  }
343
343
  exports.checkAuthToken = checkAuthToken;
344
344
  /** @internal */
345
- async function checkAppCheckToken(req, ctx) {
346
- const appCheck = req.header("X-Firebase-AppCheck");
347
- if (!appCheck) {
345
+ async function checkAppCheckToken(req, ctx, options) {
346
+ var _a;
347
+ const appCheckToken = req.header("X-Firebase-AppCheck");
348
+ if (!appCheckToken) {
348
349
  return "MISSING";
349
350
  }
350
351
  try {
351
352
  let appCheckData;
352
353
  if ((0, debug_1.isDebugFeatureEnabled)("skipTokenVerification")) {
353
- const decodedToken = unsafeDecodeAppCheckToken(appCheck);
354
+ const decodedToken = unsafeDecodeAppCheckToken(appCheckToken);
354
355
  appCheckData = { appId: decodedToken.app_id, token: decodedToken };
356
+ if (options.consumeAppCheckToken) {
357
+ appCheckData.alreadyConsumed = false;
358
+ }
355
359
  }
356
360
  else {
357
- appCheckData = await (0, app_check_1.getAppCheck)((0, app_1.getApp)()).verifyToken(appCheck);
361
+ const appCheck = (0, app_check_1.getAppCheck)((0, app_1.getApp)());
362
+ if (options.consumeAppCheckToken) {
363
+ if (((_a = appCheck.verifyToken) === null || _a === void 0 ? void 0 : _a.length) === 1) {
364
+ const errorMsg = "Unsupported version of the Admin SDK." +
365
+ " App Check token will not be consumed." +
366
+ " Please upgrade the firebase-admin to the latest version.";
367
+ logger.error(errorMsg);
368
+ throw new HttpsError("internal", "Internal Error");
369
+ }
370
+ appCheckData = await (0, app_check_1.getAppCheck)((0, app_1.getApp)()).verifyToken(appCheckToken, { consume: true });
371
+ }
372
+ else {
373
+ appCheckData = await (0, app_check_1.getAppCheck)((0, app_1.getApp)()).verifyToken(appCheckToken);
374
+ }
358
375
  }
359
376
  ctx.app = appCheckData;
360
377
  return "VALID";
361
378
  }
362
379
  catch (err) {
363
380
  logger.warn("Failed to validate AppCheck token.", err);
381
+ if (err instanceof HttpsError) {
382
+ throw err;
383
+ }
364
384
  return "INVALID";
365
385
  }
366
386
  }
@@ -409,7 +429,7 @@ function wrapOnCallHandler(options, handler) {
409
429
  delete context.rawRequest.headers[exports.ORIGINAL_AUTH_HEADER];
410
430
  }
411
431
  }
412
- const tokenStatus = await checkTokens(req, context);
432
+ const tokenStatus = await checkTokens(req, context, options);
413
433
  if (tokenStatus.auth === "INVALID") {
414
434
  throw new HttpsError("unauthenticated", "Unauthenticated");
415
435
  }
@@ -172,6 +172,29 @@ export interface RuntimeOptions {
172
172
  * When false, requests with invalid tokens set context.app to undefiend.
173
173
  */
174
174
  enforceAppCheck?: boolean;
175
+ /**
176
+ * Determines whether Firebase App Check token is consumed on request. Defaults to false.
177
+ *
178
+ * @remarks
179
+ * Set this to true to enable the App Check replay protection feature by consuming the App Check token on callable
180
+ * request. Tokens that are found to be already consumed will have request.app.alreadyConsumed property set true.
181
+ *
182
+ *
183
+ * Tokens are only considered to be consumed if it is sent to the App Check service by setting this option to true.
184
+ * Other uses of the token do not consume it.
185
+ *
186
+ * This replay protection feature requires an additional network call to the App Check backend and forces the clients
187
+ * to obtain a fresh attestation from the chosen attestation providers. This can therefore negatively impact
188
+ * performance and can potentially deplete your attestation providers' quotas faster. Use this feature only for
189
+ * protecting low volume, security critical, or expensive operations.
190
+ *
191
+ * This option does not affect the enforceAppCheck option. Setting the latter to true will cause the callable function
192
+ * to automatically respond with a 401 Unauthorized status code when request includes an invalid App Check token.
193
+ * When request includes valid but consumed App Check tokens, requests will not be automatically rejected. Instead,
194
+ * the request.app.alreadyConsumed property will be set to true and pass the execution to the handler code for making
195
+ * further decisions, such as requiring additional security checks or rejecting the request.
196
+ */
197
+ consumeAppCheckToken?: boolean;
175
198
  /**
176
199
  * Controls whether function configuration modified outside of function source is preserved. Defaults to false.
177
200
  *
package/lib/v1/index.d.ts CHANGED
@@ -18,3 +18,5 @@ export * from "./cloud-functions";
18
18
  export * from "./config";
19
19
  export * from "./function-builder";
20
20
  export * from "./function-configuration";
21
+ import * as params from "../params";
22
+ export { params };
package/lib/v1/index.js CHANGED
@@ -35,7 +35,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
35
35
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.app = exports.logger = exports.testLab = exports.tasks = exports.storage = exports.remoteConfig = exports.pubsub = exports.https = exports.firestore = exports.database = exports.auth = exports.analytics = void 0;
38
+ exports.params = exports.app = exports.logger = exports.testLab = exports.tasks = exports.storage = exports.remoteConfig = exports.pubsub = exports.https = exports.firestore = exports.database = exports.auth = exports.analytics = void 0;
39
39
  // Providers:
40
40
  const logger = require("../logger");
41
41
  exports.logger = logger;
@@ -66,3 +66,6 @@ __exportStar(require("./cloud-functions"), exports);
66
66
  __exportStar(require("./config"), exports);
67
67
  __exportStar(require("./function-builder"), exports);
68
68
  __exportStar(require("./function-configuration"), exports);
69
+ // NOTE: Equivalent to `export * as params from "../params"` but api-extractor doesn't support that syntax.
70
+ const params = require("../params");
71
+ exports.params = params;
@@ -74,6 +74,7 @@ function _onCallWithOptions(handler, options) {
74
74
  const fixedLen = (data, context) => handler(data, context);
75
75
  const func = (0, https_1.onCallHandler)({
76
76
  enforceAppCheck: options.enforceAppCheck,
77
+ consumeAppCheckToken: options.consumeAppCheckToken,
77
78
  cors: { origin: true, methods: "POST" },
78
79
  }, fixedLen);
79
80
  func.__trigger = {
package/lib/v2/index.d.ts CHANGED
@@ -22,3 +22,5 @@ export { alerts, database, storage, https, identity, pubsub, logger, tasks, even
22
22
  export { setGlobalOptions, GlobalOptions, SupportedRegion, MemoryOption, VpcEgressSetting, IngressSetting, EventHandlerOptions, } from "./options";
23
23
  export { CloudFunction, CloudEvent, ParamsOf } from "./core";
24
24
  export { Change } from "../common/change";
25
+ import * as params from "../params";
26
+ export { params };
package/lib/v2/index.js CHANGED
@@ -21,7 +21,7 @@
21
21
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  // SOFTWARE.
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.Change = exports.setGlobalOptions = exports.firestore = exports.testLab = exports.remoteConfig = exports.scheduler = exports.eventarc = exports.tasks = exports.logger = exports.pubsub = exports.identity = exports.https = exports.storage = exports.database = exports.alerts = void 0;
24
+ exports.params = exports.Change = exports.setGlobalOptions = exports.firestore = exports.testLab = exports.remoteConfig = exports.scheduler = exports.eventarc = exports.tasks = exports.logger = exports.pubsub = exports.identity = exports.https = exports.storage = exports.database = exports.alerts = void 0;
25
25
  /**
26
26
  * The V2 API for Cloud Functions for Firebase.
27
27
  * This SDK also supports deep imports. For example, the namespace
@@ -59,3 +59,6 @@ var options_1 = require("./options");
59
59
  Object.defineProperty(exports, "setGlobalOptions", { enumerable: true, get: function () { return options_1.setGlobalOptions; } });
60
60
  var change_1 = require("../common/change");
61
61
  Object.defineProperty(exports, "Change", { enumerable: true, get: function () { return change_1.Change; } });
62
+ // NOTE: Equivalent to `export * as params from "../params"` but api-extractor doesn't support that syntax.
63
+ const params = require("../params");
64
+ exports.params = params;
@@ -111,7 +111,7 @@ export interface GlobalOptions {
111
111
  * @remarks
112
112
  * When true, requests with invalid tokens autorespond with a 401
113
113
  * (Unauthorized) error.
114
- * When false, requests with invalid tokens set event.app to undefiend.
114
+ * When false, requests with invalid tokens set event.app to undefined.
115
115
  */
116
116
  enforceAppCheck?: boolean;
117
117
  /**
@@ -31,7 +31,7 @@ export interface AlertEvent<T> extends CloudEvent<FirebaseAlertData<T>> {
31
31
  data: FirebaseAlertData<T>;
32
32
  }
33
33
  /** The underlying alert type of the Firebase Alerts provider. */
34
- export type AlertType = "crashlytics.newFatalIssue" | "crashlytics.newNonfatalIssue" | "crashlytics.regression" | "crashlytics.stabilityDigest" | "crashlytics.velocity" | "crashlytics.newAnrIssue" | "billing.planUpdate" | "billing.automatedPlanUpdate" | "appDistribution.newTesterIosDevice" | "appDistribution.inAppFeedback" | "performance.threshold" | string;
34
+ export type AlertType = "crashlytics.newFatalIssue" | "crashlytics.newNonfatalIssue" | "crashlytics.regression" | "crashlytics.stabilityDigest" | "crashlytics.velocity" | "crashlytics.newAnrIssue" | "billing.planUpdate" | "billing.planAutomatedUpdate" | "appDistribution.newTesterIosDevice" | "appDistribution.inAppFeedback" | "performance.threshold" | string;
35
35
  /**
36
36
  * Configuration for Firebase Alert functions.
37
37
  */
@@ -2,6 +2,7 @@ import * as firestore from "firebase-admin/firestore";
2
2
  import { ParamsOf } from "../../common/params";
3
3
  import { Change, CloudEvent, CloudFunction } from "../core";
4
4
  import { EventHandlerOptions } from "../options";
5
+ export { Change };
5
6
  /** A Firestore DocumentSnapshot */
6
7
  export type DocumentSnapshot = firestore.DocumentSnapshot;
7
8
  /** A Firestore QueryDocumentSnapshot */
@@ -21,12 +21,13 @@
21
21
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  // SOFTWARE.
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.onChangedOperation = exports.onOperation = exports.makeEndpoint = exports.makeChangedFirestoreEvent = exports.makeFirestoreEvent = exports.makeParams = exports.createBeforeSnapshot = exports.createSnapshot = exports.getOpts = exports.onDocumentDeleted = exports.onDocumentUpdated = exports.onDocumentCreated = exports.onDocumentWritten = exports.deletedEventType = exports.updatedEventType = exports.createdEventType = exports.writtenEventType = void 0;
24
+ exports.onChangedOperation = exports.onOperation = exports.makeEndpoint = exports.makeChangedFirestoreEvent = exports.makeFirestoreEvent = exports.makeParams = exports.createBeforeSnapshot = exports.createSnapshot = exports.getOpts = exports.onDocumentDeleted = exports.onDocumentUpdated = exports.onDocumentCreated = exports.onDocumentWritten = exports.deletedEventType = exports.updatedEventType = exports.createdEventType = exports.writtenEventType = exports.Change = void 0;
25
25
  const logger = require("../../logger");
26
26
  const path_1 = require("../../common/utilities/path");
27
27
  const path_pattern_1 = require("../../common/utilities/path-pattern");
28
28
  const manifest_1 = require("../../runtime/manifest");
29
29
  const core_1 = require("../core");
30
+ Object.defineProperty(exports, "Change", { enumerable: true, get: function () { return core_1.Change; } });
30
31
  const options_1 = require("../options");
31
32
  const firestore_1 = require("../../common/providers/firestore");
32
33
  /** @internal */
@@ -109,6 +109,29 @@ export interface CallableOptions extends HttpsOptions {
109
109
  * When false, requests with invalid tokens set event.app to undefiend.
110
110
  */
111
111
  enforceAppCheck?: boolean;
112
+ /**
113
+ * Determines whether Firebase App Check token is consumed on request. Defaults to false.
114
+ *
115
+ * @remarks
116
+ * Set this to true to enable the App Check replay protection feature by consuming the App Check token on callable
117
+ * request. Tokens that are found to be already consumed will have request.app.alreadyConsumed property set true.
118
+ *
119
+ *
120
+ * Tokens are only considered to be consumed if it is sent to the App Check service by setting this option to true.
121
+ * Other uses of the token do not consume it.
122
+ *
123
+ * This replay protection feature requires an additional network call to the App Check backend and forces the clients
124
+ * to obtain a fresh attestation from the chosen attestation providers. This can therefore negatively impact
125
+ * performance and can potentially deplete your attestation providers' quotas faster. Use this feature only for
126
+ * protecting low volume, security critical, or expensive operations.
127
+ *
128
+ * This option does not affect the enforceAppCheck option. Setting the latter to true will cause the callable function
129
+ * to automatically respond with a 401 Unauthorized status code when request includes an invalid App Check token.
130
+ * When request includes valid but consumed App Check tokens, requests will not be automatically rejected. Instead,
131
+ * the request.app.alreadyConsumed property will be set to true and pass the execution to the handler code for making
132
+ * further decisions, such as requiring additional security checks or rejecting the request.
133
+ */
134
+ consumeAppCheckToken?: boolean;
112
135
  }
113
136
  /**
114
137
  * Handles HTTPS requests.
@@ -119,6 +119,7 @@ function onCall(optsOrHandler, handler) {
119
119
  const func = (0, https_1.onCallHandler)({
120
120
  cors: { origin, methods: "POST" },
121
121
  enforceAppCheck: (_a = opts.enforceAppCheck) !== null && _a !== void 0 ? _a : options.getGlobalOptions().enforceAppCheck,
122
+ consumeAppCheckToken: opts.consumeAppCheckToken,
122
123
  }, fixedLen);
123
124
  Object.defineProperty(func, "__trigger", {
124
125
  get: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-functions",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "Firebase SDK for Cloud Functions",
5
5
  "keywords": [
6
6
  "firebase",
@@ -225,7 +225,7 @@
225
225
  "eslint-config-prettier": "^8.3.0",
226
226
  "eslint-plugin-jsdoc": "^39.2.9",
227
227
  "eslint-plugin-prettier": "^4.0.0",
228
- "firebase-admin": "^10.3.0",
228
+ "firebase-admin": "^11.8.0",
229
229
  "js-yaml": "^3.13.1",
230
230
  "jsdom": "^16.2.1",
231
231
  "jsonwebtoken": "^9.0.0",