firebase-tools 9.20.0 → 9.21.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/lib/apiv2.js +4 -2
  3. package/lib/commands/crashlytics-symbols-upload.js +1 -1
  4. package/lib/commands/ext-dev-unpublish.js +10 -3
  5. package/lib/commands/functions-delete.js +53 -42
  6. package/lib/commands/functions-list.js +11 -11
  7. package/lib/deploy/functions/backend.js +77 -115
  8. package/lib/deploy/functions/checkIam.js +8 -8
  9. package/lib/deploy/functions/deploy.js +4 -10
  10. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  11. package/lib/deploy/functions/prepare.js +61 -29
  12. package/lib/deploy/functions/pricing.js +17 -17
  13. package/lib/deploy/functions/prompts.js +22 -21
  14. package/lib/deploy/functions/release/executor.js +39 -0
  15. package/lib/deploy/functions/release/fabricator.js +362 -0
  16. package/lib/deploy/functions/release/index.js +69 -0
  17. package/lib/deploy/functions/release/planner.js +159 -0
  18. package/lib/deploy/functions/release/reporter.js +162 -0
  19. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  20. package/lib/deploy/functions/release/timer.js +14 -0
  21. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +102 -127
  22. package/lib/deploy/functions/runtimes/node/parseTriggers.js +22 -43
  23. package/lib/deploy/functions/triggerRegionHelper.js +28 -20
  24. package/lib/deploy/functions/validate.js +1 -24
  25. package/lib/emulator/auth/apiSpec.js +37 -6
  26. package/lib/emulator/auth/operations.js +18 -8
  27. package/lib/emulator/auth/server.js +16 -2
  28. package/lib/emulator/auth/state.js +34 -15
  29. package/lib/emulator/downloadableEmulators.js +7 -7
  30. package/lib/emulator/functionsEmulator.js +15 -3
  31. package/lib/emulator/storage/cloudFunctions.js +37 -7
  32. package/lib/extensions/extensionsHelper.js +1 -1
  33. package/lib/gcp/cloudfunctions.js +1 -68
  34. package/lib/gcp/cloudfunctionsv2.js +2 -94
  35. package/lib/gcp/cloudscheduler.js +22 -16
  36. package/lib/gcp/pubsub.js +1 -9
  37. package/lib/utils.js +30 -1
  38. package/package.json +1 -1
  39. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  40. package/lib/deploy/functions/deploymentTimer.js +0 -23
  41. package/lib/deploy/functions/errorHandler.js +0 -75
  42. package/lib/deploy/functions/release.js +0 -116
  43. package/lib/deploy/functions/tasks.js +0 -324
  44. package/lib/functions/listFunctions.js +0 -10
  45. package/lib/functionsDelete.js +0 -60
@@ -3580,6 +3580,37 @@ exports.default = {
3580
3580
  tags: ["emulator"],
3581
3581
  },
3582
3582
  },
3583
+ "/emulator/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts": {
3584
+ parameters: [
3585
+ {
3586
+ name: "targetProjectId",
3587
+ in: "path",
3588
+ description: "The ID of the Google Cloud project that the accounts belong to.",
3589
+ required: true,
3590
+ schema: { type: "string" },
3591
+ },
3592
+ {
3593
+ name: "tenantId",
3594
+ in: "path",
3595
+ description: "The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned.",
3596
+ required: true,
3597
+ schema: { type: "string" },
3598
+ },
3599
+ ],
3600
+ servers: [{ url: "" }],
3601
+ delete: {
3602
+ description: "Remove all accounts in the project, regardless of state.",
3603
+ operationId: "emulator.projects.accounts.delete",
3604
+ responses: {
3605
+ 200: {
3606
+ description: "Successful response",
3607
+ content: { "application/json": { schema: { type: "object" } } },
3608
+ },
3609
+ },
3610
+ security: [],
3611
+ tags: ["emulator"],
3612
+ },
3613
+ },
3583
3614
  "/emulator/v1/projects/{targetProjectId}/config": {
3584
3615
  parameters: [
3585
3616
  {
@@ -4978,7 +5009,7 @@ exports.default = {
4978
5009
  type: "string",
4979
5010
  },
4980
5011
  postBody: {
4981
- description: "If the user is signing in with an authorization response obtained via a previous CreateAuthUri authorization request, this is the body of the HTTP POST callback from the IdP, if present. Otherwise, if the user is signing in with a manually provided IdP credential, this should be a URL-encoded form that contains the credential (e.g. an ID token or access token for OAuth 2.0 IdPs) and the provider ID of the IdP that issued the credential. For example, if the user is signing in to the Google provider using a Google ID token, this should be set to `id_token=[GOOGLE_ID_TOKEN]&providerId=google.com`, where `[GOOGLE_ID_TOKEN]` should be replaced with the Google ID token. If the user is signing in to the Facebook provider using a Facebook access token, this should be set to `access_token=[FACEBOOK_ACCESS_TOKEN]&providerId=facebook.com`, where `[FACEBOOK_ACCESS_TOKEN]` should be replaced with the Facebook access token. If the user is signing in to the Twitter provider using a Twitter OAuth 1.0 credential, this should be set to `access_token=[TWITTER_ACCESS_TOKEN]&oauth_token_secret=[TWITTER_TOKEN_SECRET]&providerId=twitter.com`, where `[TWITTER_ACCESS_TOKEN]` and `[TWITTER_TOKEN_SECRET]` should be replaced with the Twitter OAuth access token and Twitter OAuth token secret respectively.",
5012
+ description: "If the user is signing in with an authorization response obtained via a previous CreateAuthUri authorization request, this is the body of the HTTP POST callback from the IdP, if present. Otherwise, if the user is signing in with a manually provided IdP credential, this should be a URL-encoded form that contains the credential (e.g. an ID token or access token for OAuth 2.0 IdPs) and the provider ID of the IdP that issued the credential. For example, if the user is signing in to the Google provider using a Google ID token, this should be set to `id_token=[GOOGLE_ID_TOKEN]&providerId=google.com`, where `[GOOGLE_ID_TOKEN]` should be replaced with the Google ID token. If the user is signing in to the Facebook provider using a Facebook authentication token, this should be set to `id_token=[FACEBOOK_AUTHENTICATION_TOKEN]&providerId=facebook.com&nonce= [NONCE]`, where `[FACEBOOK_AUTHENTICATION_TOKEN]` should be replaced with the Facebook authentication token. Nonce is required for validating the token. The request will fail if no nonce is provided. If the user is signing in to the Facebook provider using a Facebook access token, this should be set to `access_token=[FACEBOOK_ACCESS_TOKEN]&providerId=facebook.com`, where `[FACEBOOK_ACCESS_TOKEN]` should be replaced with the Facebook access token. If the user is signing in to the Twitter provider using a Twitter OAuth 1.0 credential, this should be set to `access_token=[TWITTER_ACCESS_TOKEN]&oauth_token_secret=[TWITTER_TOKEN_SECRET]&providerId=twitter.com`, where `[TWITTER_ACCESS_TOKEN]` and `[TWITTER_TOKEN_SECRET]` should be replaced with the Twitter OAuth access token and Twitter OAuth token secret respectively.",
4982
5013
  type: "string",
4983
5014
  },
4984
5015
  requestUri: {
@@ -6665,16 +6696,16 @@ exports.default = {
6665
6696
  type: "object",
6666
6697
  },
6667
6698
  GoogleIamV1Binding: {
6668
- description: "Associates `members` with a `role`.",
6699
+ description: "Associates `members`, or principals, with a `role`.",
6669
6700
  properties: {
6670
6701
  condition: { $ref: "#/components/schemas/GoogleTypeExpr" },
6671
6702
  members: {
6672
- description: "Specifies the identities requesting access for a Cloud Platform resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. ",
6703
+ description: "Specifies the principals requesting access for a Cloud Platform resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. ",
6673
6704
  items: { type: "string" },
6674
6705
  type: "array",
6675
6706
  },
6676
6707
  role: {
6677
- description: "Role that is assigned to `members`. For example, `roles/viewer`, `roles/editor`, or `roles/owner`.",
6708
+ description: "Role that is assigned to the list of `members`, or principals. For example, `roles/viewer`, `roles/editor`, or `roles/owner`.",
6678
6709
  type: "string",
6679
6710
  },
6680
6711
  },
@@ -6697,7 +6728,7 @@ exports.default = {
6697
6728
  type: "object",
6698
6729
  },
6699
6730
  GoogleIamV1Policy: {
6700
- description: 'An Identity and Access Management (IAM) policy, which specifies access controls for Google Cloud resources. A `Policy` is a collection of `bindings`. A `binding` binds one or more `members` to a single `role`. Members can be user accounts, service accounts, Google groups, and domains (such as G Suite). A `role` is a named list of permissions; each `role` can be an IAM predefined role or a user-created custom role. For some types of Google Cloud resources, a `binding` can also specify a `condition`, which is a logical expression that allows access to a resource only if the expression evaluates to `true`. A condition can add constraints based on attributes of the request, the resource, or both. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). **JSON example:** { "bindings": [ { "role": "roles/resourcemanager.organizationAdmin", "members": [ "user:mike@example.com", "group:admins@example.com", "domain:google.com", "serviceAccount:my-project-id@appspot.gserviceaccount.com" ] }, { "role": "roles/resourcemanager.organizationViewer", "members": [ "user:eve@example.com" ], "condition": { "title": "expirable access", "description": "Does not grant access after Sep 2020", "expression": "request.time < timestamp(\'2020-10-01T00:00:00.000Z\')", } } ], "etag": "BwWWja0YfJA=", "version": 3 } **YAML example:** bindings: - members: - user:mike@example.com - group:admins@example.com - domain:google.com - serviceAccount:my-project-id@appspot.gserviceaccount.com role: roles/resourcemanager.organizationAdmin - members: - user:eve@example.com role: roles/resourcemanager.organizationViewer condition: title: expirable access description: Does not grant access after Sep 2020 expression: request.time < timestamp(\'2020-10-01T00:00:00.000Z\') etag: BwWWja0YfJA= version: 3 For a description of IAM and its features, see the [IAM documentation](https://cloud.google.com/iam/docs/).',
6731
+ description: 'An Identity and Access Management (IAM) policy, which specifies access controls for Google Cloud resources. A `Policy` is a collection of `bindings`. A `binding` binds one or more `members`, or principals, to a single `role`. Principals can be user accounts, service accounts, Google groups, and domains (such as G Suite). A `role` is a named list of permissions; each `role` can be an IAM predefined role or a user-created custom role. For some types of Google Cloud resources, a `binding` can also specify a `condition`, which is a logical expression that allows access to a resource only if the expression evaluates to `true`. A condition can add constraints based on attributes of the request, the resource, or both. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). **JSON example:** { "bindings": [ { "role": "roles/resourcemanager.organizationAdmin", "members": [ "user:mike@example.com", "group:admins@example.com", "domain:google.com", "serviceAccount:my-project-id@appspot.gserviceaccount.com" ] }, { "role": "roles/resourcemanager.organizationViewer", "members": [ "user:eve@example.com" ], "condition": { "title": "expirable access", "description": "Does not grant access after Sep 2020", "expression": "request.time < timestamp(\'2020-10-01T00:00:00.000Z\')", } } ], "etag": "BwWWja0YfJA=", "version": 3 } **YAML example:** bindings: - members: - user:mike@example.com - group:admins@example.com - domain:google.com - serviceAccount:my-project-id@appspot.gserviceaccount.com role: roles/resourcemanager.organizationAdmin - members: - user:eve@example.com role: roles/resourcemanager.organizationViewer condition: title: expirable access description: Does not grant access after Sep 2020 expression: request.time < timestamp(\'2020-10-01T00:00:00.000Z\') etag: BwWWja0YfJA= version: 3 For a description of IAM and its features, see the [IAM documentation](https://cloud.google.com/iam/docs/).',
6701
6732
  properties: {
6702
6733
  auditConfigs: {
6703
6734
  description: "Specifies cloud audit logging configuration for this policy.",
@@ -6705,7 +6736,7 @@ exports.default = {
6705
6736
  type: "array",
6706
6737
  },
6707
6738
  bindings: {
6708
- description: "Associates a list of `members` to a `role`. Optionally, may specify a `condition` that determines how and when the `bindings` are applied. Each of the `bindings` must contain at least one member. The `bindings` in a `Policy` can refer to up to 1,500 members; up to 250 of these members can be Google groups. Each occurrence of a member counts towards these limits. For example, if the `bindings` grant 50 different roles to `user:alice@example.com`, and not to any other member, then you can add another 1,450 members to the `bindings` in the `Policy`.",
6739
+ description: "Associates a list of `members`, or principals, with a `role`. Optionally, may specify a `condition` that determines how and when the `bindings` are applied. Each of the `bindings` must contain at least one principal. The `bindings` in a `Policy` can refer to up to 1,500 principals; up to 250 of these principals can be Google groups. Each occurrence of a principal counts towards these limits. For example, if the `bindings` grant 50 different roles to `user:alice@example.com`, and not to any other principal, then you can add another 1,450 principals to the `bindings` in the `Policy`.",
6709
6740
  items: { $ref: "#/components/schemas/GoogleIamV1Binding" },
6710
6741
  type: "array",
6711
6742
  },
@@ -160,8 +160,8 @@ function signUp(state, reqBody, ctx) {
160
160
  generateEnrollmentIds: true,
161
161
  });
162
162
  }
163
- if (reqBody.tenantId) {
164
- updates.tenantId = reqBody.tenantId;
163
+ if (state instanceof state_1.TenantProjectState) {
164
+ updates.tenantId = state.tenantId;
165
165
  }
166
166
  let user;
167
167
  if (reqBody.idToken) {
@@ -267,6 +267,8 @@ function batchCreate(state, reqBody) {
267
267
  };
268
268
  if (userInfo.tenantId) {
269
269
  errors_1.assert(state instanceof state_1.TenantProjectState && state.tenantId === userInfo.tenantId, "Tenant id in userInfo does not match the tenant id in request.");
270
+ }
271
+ if (state instanceof state_1.TenantProjectState) {
270
272
  fields.tenantId = state.tenantId;
271
273
  }
272
274
  if (userInfo.passwordHash) {
@@ -921,7 +923,7 @@ function signInWithCustomToken(state, reqBody) {
921
923
  else {
922
924
  const decoded = jsonwebtoken_1.decode(reqBody.token, { complete: true });
923
925
  if (state instanceof state_1.TenantProjectState) {
924
- errors_1.assert((decoded === null || decoded === void 0 ? void 0 : decoded.payload.tenantId) === state.tenantId, "TENANT_ID_MISMATCH");
926
+ errors_1.assert((decoded === null || decoded === void 0 ? void 0 : decoded.payload.tenant_id) === state.tenantId, "TENANT_ID_MISMATCH");
925
927
  }
926
928
  errors_1.assert(decoded, "INVALID_CUSTOM_TOKEN : Invalid assertion format");
927
929
  if (decoded.header.alg !== "none") {
@@ -1798,16 +1800,24 @@ function parsePendingCredential(state, pendingCredential) {
1798
1800
  return { user, signInProvider };
1799
1801
  }
1800
1802
  function createTenant(state, reqBody) {
1803
+ var _a, _b, _c, _d, _e;
1801
1804
  if (!(state instanceof state_1.AgentProjectState)) {
1802
1805
  throw new errors_1.InternalError("INTERNAL_ERROR: Can only create tenant in agent project", "INTERNAL");
1803
1806
  }
1807
+ const mfaConfig = (_a = reqBody.mfaConfig) !== null && _a !== void 0 ? _a : {};
1808
+ if (!("state" in mfaConfig)) {
1809
+ mfaConfig.state = "DISABLED";
1810
+ }
1811
+ if (!("enabledProviders" in mfaConfig)) {
1812
+ mfaConfig.enabledProviders = [];
1813
+ }
1804
1814
  const tenant = {
1805
1815
  displayName: reqBody.displayName,
1806
- allowPasswordSignup: reqBody.allowPasswordSignup,
1807
- enableEmailLinkSignin: reqBody.enableEmailLinkSignin,
1808
- enableAnonymousUser: reqBody.enableAnonymousUser,
1809
- disableAuth: reqBody.disableAuth,
1810
- mfaConfig: reqBody.mfaConfig,
1816
+ allowPasswordSignup: (_b = reqBody.allowPasswordSignup) !== null && _b !== void 0 ? _b : false,
1817
+ enableEmailLinkSignin: (_c = reqBody.enableEmailLinkSignin) !== null && _c !== void 0 ? _c : false,
1818
+ enableAnonymousUser: (_d = reqBody.enableAnonymousUser) !== null && _d !== void 0 ? _d : false,
1819
+ disableAuth: (_e = reqBody.disableAuth) !== null && _e !== void 0 ? _e : false,
1820
+ mfaConfig: mfaConfig,
1811
1821
  tenantId: "",
1812
1822
  };
1813
1823
  return state.createTenant(tenant);
@@ -17,6 +17,7 @@ const lodash_1 = require("lodash");
17
17
  const handlers_1 = require("./handlers");
18
18
  const bodyParser = require("body-parser");
19
19
  const url_1 = require("url");
20
+ const jsonwebtoken_1 = require("jsonwebtoken");
20
21
  const apiSpec = apiSpec_1.default;
21
22
  const API_SPEC_PATH = "/emulator/openapi.json";
22
23
  const AUTH_HEADER_PREFIX = "bearer ";
@@ -70,6 +71,10 @@ async function createApp(defaultProjectId, projectStateForId = new Map()) {
70
71
  const app = express();
71
72
  app.set("json spaces", 2);
72
73
  app.use(cors({ origin: true }));
74
+ app.delete("*", (req, _, next) => {
75
+ delete req.headers["content-type"];
76
+ next();
77
+ });
73
78
  app.get("/", (req, res) => {
74
79
  return res.json({
75
80
  authEmulator: {
@@ -334,7 +339,7 @@ function toExegesisController(ops, getProjectStateById) {
334
339
  }
335
340
  function toExegesisOperation(operation) {
336
341
  return (ctx) => {
337
- var _a, _b, _c, _d, _e;
342
+ var _a, _b, _c, _d, _e, _f, _g;
338
343
  let targetProjectId = ctx.params.path.targetProjectId || ((_a = ctx.requestBody) === null || _a === void 0 ? void 0 : _a.targetProjectId);
339
344
  if (targetProjectId) {
340
345
  if ((_b = ctx.api.operationObject.security) === null || _b === void 0 ? void 0 : _b.some((sec) => sec.Oauth2)) {
@@ -344,10 +349,19 @@ function toExegesisController(ops, getProjectStateById) {
344
349
  else {
345
350
  targetProjectId = ctx.user;
346
351
  }
352
+ let targetTenantId = undefined;
347
353
  if (ctx.params.path.tenantId && ((_d = ctx.requestBody) === null || _d === void 0 ? void 0 : _d.tenantId)) {
348
354
  errors_2.assert(ctx.params.path.tenantId === ctx.requestBody.tenantId, "TENANT_ID_MISMATCH");
349
355
  }
350
- const targetTenantId = ctx.params.path.tenantId || ((_e = ctx.requestBody) === null || _e === void 0 ? void 0 : _e.tenantId);
356
+ targetTenantId = ctx.params.path.tenantId || ((_e = ctx.requestBody) === null || _e === void 0 ? void 0 : _e.tenantId);
357
+ if ((_f = ctx.requestBody) === null || _f === void 0 ? void 0 : _f.idToken) {
358
+ const idToken = (_g = ctx.requestBody) === null || _g === void 0 ? void 0 : _g.idToken;
359
+ const decoded = jsonwebtoken_1.decode(idToken, { complete: true });
360
+ if ((decoded === null || decoded === void 0 ? void 0 : decoded.payload.firebase.tenant) && targetTenantId) {
361
+ errors_2.assert((decoded === null || decoded === void 0 ? void 0 : decoded.payload.firebase.tenant) === targetTenantId, "TENANT_ID_MISMATCH");
362
+ }
363
+ targetTenantId = targetTenantId || (decoded === null || decoded === void 0 ? void 0 : decoded.payload.firebase.tenant);
364
+ }
351
365
  return operation(getProjectStateById(targetProjectId, targetTenantId), ctx.requestBody, ctx);
352
366
  };
353
367
  }
@@ -459,7 +459,17 @@ class AgentProjectState extends ProjectState {
459
459
  }
460
460
  getTenantProject(tenantId) {
461
461
  if (!this.tenantProjectForTenantId.has(tenantId)) {
462
- this.createTenantWithTenantId(tenantId, { tenantId });
462
+ this.createTenantWithTenantId(tenantId, {
463
+ tenantId,
464
+ allowPasswordSignup: true,
465
+ disableAuth: false,
466
+ mfaConfig: {
467
+ state: "ENABLED",
468
+ enabledProviders: ["PHONE_SMS"],
469
+ },
470
+ enableAnonymousUser: true,
471
+ enableEmailLinkSignin: true,
472
+ });
463
473
  }
464
474
  return this.tenantProjectForTenantId.get(tenantId);
465
475
  }
@@ -525,34 +535,43 @@ class TenantProjectState extends ProjectState {
525
535
  return this._tenantConfig;
526
536
  }
527
537
  get allowPasswordSignup() {
528
- var _a;
529
- return (_a = this._tenantConfig.allowPasswordSignup) !== null && _a !== void 0 ? _a : true;
538
+ return this._tenantConfig.allowPasswordSignup;
530
539
  }
531
540
  get disableAuth() {
532
- var _a;
533
- return (_a = this._tenantConfig.disableAuth) !== null && _a !== void 0 ? _a : false;
541
+ return this._tenantConfig.disableAuth;
534
542
  }
535
543
  get mfaConfig() {
536
- var _a;
537
- return ((_a = this._tenantConfig.mfaConfig) !== null && _a !== void 0 ? _a : {
538
- state: "ENABLED",
539
- enabledProviders: ["PHONE_SMS"],
540
- });
544
+ return this._tenantConfig.mfaConfig;
541
545
  }
542
546
  get enableAnonymousUser() {
543
- var _a;
544
- return (_a = this._tenantConfig.enableAnonymousUser) !== null && _a !== void 0 ? _a : true;
547
+ return this._tenantConfig.enableAnonymousUser;
545
548
  }
546
549
  get enableEmailLinkSignin() {
547
- var _a;
548
- return (_a = this._tenantConfig.enableEmailLinkSignin) !== null && _a !== void 0 ? _a : true;
550
+ return this._tenantConfig.enableEmailLinkSignin;
549
551
  }
550
552
  delete() {
551
553
  this.parentProject.deleteTenant(this.tenantId);
552
554
  }
553
555
  updateTenant(update, updateMask) {
556
+ var _a, _b, _c, _d, _e;
554
557
  if (!updateMask) {
555
- this._tenantConfig = Object.assign(Object.assign({}, update), { tenantId: this.tenantId, name: this.tenantConfig.name });
558
+ const mfaConfig = (_a = update.mfaConfig) !== null && _a !== void 0 ? _a : {};
559
+ if (!("state" in mfaConfig)) {
560
+ mfaConfig.state = "DISABLED";
561
+ }
562
+ if (!("enabledProviders" in mfaConfig)) {
563
+ mfaConfig.enabledProviders = [];
564
+ }
565
+ this._tenantConfig = {
566
+ tenantId: this.tenantId,
567
+ name: this.tenantConfig.name,
568
+ allowPasswordSignup: (_b = update.allowPasswordSignup) !== null && _b !== void 0 ? _b : false,
569
+ disableAuth: (_c = update.disableAuth) !== null && _c !== void 0 ? _c : false,
570
+ mfaConfig: mfaConfig,
571
+ enableAnonymousUser: (_d = update.enableAnonymousUser) !== null && _d !== void 0 ? _d : false,
572
+ enableEmailLinkSignin: (_e = update.enableEmailLinkSignin) !== null && _e !== void 0 ? _e : false,
573
+ displayName: update.displayName,
574
+ };
556
575
  return this.tenantConfig;
557
576
  }
558
577
  const paths = updateMask.split(",");
@@ -50,15 +50,15 @@ exports.DownloadDetails = {
50
50
  },
51
51
  },
52
52
  ui: {
53
- version: "1.6.3",
54
- downloadPath: path.join(CACHE_DIR, "ui-v1.6.3.zip"),
55
- unzipDir: path.join(CACHE_DIR, "ui-v1.6.3"),
56
- binaryPath: path.join(CACHE_DIR, "ui-v1.6.3", "server.bundle.js"),
53
+ version: "1.6.4",
54
+ downloadPath: path.join(CACHE_DIR, "ui-v1.6.4.zip"),
55
+ unzipDir: path.join(CACHE_DIR, "ui-v1.6.4"),
56
+ binaryPath: path.join(CACHE_DIR, "ui-v1.6.4", "server.bundle.js"),
57
57
  opts: {
58
58
  cacheDir: CACHE_DIR,
59
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.3.zip",
60
- expectedSize: 3757268,
61
- expectedChecksum: "153090a46072545aadeb307397cc8f45",
59
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.4.zip",
60
+ expectedSize: 3757300,
61
+ expectedChecksum: "20d4ee71e4ff7527b1843b6a8636142e",
62
62
  namePrefix: "ui",
63
63
  },
64
64
  },
@@ -123,10 +123,22 @@ class FunctionsEmulator {
123
123
  });
124
124
  };
125
125
  const multicastHandler = (req, res) => {
126
- const reqBody = req.rawBody;
127
- const proto = JSON.parse(reqBody.toString());
128
- const triggers = this.multicastTriggers[`${this.args.projectId}:${proto.eventType}`] || [];
126
+ var _a;
129
127
  const projectId = req.params.project_id;
128
+ const reqBody = req.rawBody;
129
+ let proto = JSON.parse(reqBody.toString());
130
+ let triggerKey;
131
+ if ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.includes("cloudevent")) {
132
+ triggerKey = `${this.args.projectId}:${proto.type}`;
133
+ if (types_2.EventUtils.isBinaryCloudEvent(req)) {
134
+ proto = types_2.EventUtils.extractBinaryCloudEventContext(req);
135
+ proto.data = req.body;
136
+ }
137
+ }
138
+ else {
139
+ triggerKey = `${this.args.projectId}:${proto.eventType}`;
140
+ }
141
+ const triggers = this.multicastTriggers[triggerKey] || [];
130
142
  triggers.forEach((triggerId) => {
131
143
  this.workQueue.submit(() => {
132
144
  this.logger.log("DEBUG", `Accepted multicast request ${req.method} ${req.url} --> ${triggerId}`);
@@ -6,6 +6,12 @@ const types_1 = require("../types");
6
6
  const emulatorLogger_1 = require("../emulatorLogger");
7
7
  const metadata_1 = require("./metadata");
8
8
  const apiv2_1 = require("../../apiv2");
9
+ const STORAGE_V2_ACTION_MAP = {
10
+ finalize: "finalized",
11
+ metadataUpdate: "metadataUpdated",
12
+ delete: "deleted",
13
+ archive: "archived",
14
+ };
9
15
  class StorageCloudFunctions {
10
16
  constructor(projectId) {
11
17
  this.projectId = projectId;
@@ -19,26 +25,37 @@ class StorageCloudFunctions {
19
25
  this.functionsEmulatorInfo = functionsEmulator.getInfo();
20
26
  this.multicastOrigin = `http://${registry_1.EmulatorRegistry.getInfoHostString(this.functionsEmulatorInfo)}`;
21
27
  this.multicastPath = `/functions/projects/${projectId}/trigger_multicast`;
28
+ this.client = new apiv2_1.Client({ urlPrefix: this.multicastOrigin, auth: false });
22
29
  }
23
30
  }
24
31
  async dispatch(action, object) {
25
- if (!this.enabled)
32
+ if (!this.enabled) {
26
33
  return;
27
- const multicastEventBody = this.createEventRequestBody(action, object);
28
- const c = new apiv2_1.Client({ urlPrefix: this.multicastOrigin, auth: false });
29
- let res;
34
+ }
35
+ const errStatus = [];
30
36
  let err;
31
37
  try {
32
- res = await c.post(this.multicastPath, multicastEventBody);
38
+ const eventBody = this.createLegacyEventRequestBody(action, object);
39
+ const eventRes = await this.client.post(this.multicastPath, eventBody);
40
+ if (eventRes.status !== 200) {
41
+ errStatus.push(eventRes.status);
42
+ }
43
+ const cloudEventBody = this.createCloudEventRequestBody(action, object);
44
+ const cloudEventRes = await this.client.post(this.multicastPath, cloudEventBody, {
45
+ headers: { "Content-Type": "application/cloudevents+json; charset=UTF-8" },
46
+ });
47
+ if (cloudEventRes.status !== 200) {
48
+ errStatus.push(cloudEventRes.status);
49
+ }
33
50
  }
34
51
  catch (e) {
35
52
  err = e;
36
53
  }
37
- if (err || (res === null || res === void 0 ? void 0 : res.status) != 200) {
54
+ if (err || errStatus.length > 0) {
38
55
  this.logger.logLabeled("WARN", "functions", `Firebase Storage function was not triggered due to emulation error. Please file a bug.`);
39
56
  }
40
57
  }
41
- createEventRequestBody(action, objectMetadataPayload) {
58
+ createLegacyEventRequestBody(action, objectMetadataPayload) {
42
59
  const timestamp = new Date();
43
60
  return JSON.stringify({
44
61
  eventId: `${timestamp.getTime()}`,
@@ -52,5 +69,18 @@ class StorageCloudFunctions {
52
69
  data: objectMetadataPayload,
53
70
  });
54
71
  }
72
+ createCloudEventRequestBody(action, objectMetadataPayload) {
73
+ const ceAction = STORAGE_V2_ACTION_MAP[action];
74
+ if (!ceAction) {
75
+ throw new Error("Action is not definied as a CloudEvents action");
76
+ }
77
+ const data = objectMetadataPayload;
78
+ return JSON.stringify({
79
+ specVersion: 1,
80
+ type: `google.cloud.storage.object.v1.${ceAction}`,
81
+ source: `//storage.googleapis.com/projects/_/buckets/${objectMetadataPayload.bucket}/objects/${objectMetadataPayload.name}`,
82
+ data,
83
+ });
84
+ }
55
85
  }
56
86
  exports.StorageCloudFunctions = StorageCloudFunctions;
@@ -307,7 +307,7 @@ async function publishExtensionVersionFromLocalSource(args) {
307
307
  else if (extension &&
308
308
  extension.latestVersion &&
309
309
  semver.eq(extensionSpec.version, extension.latestVersion)) {
310
- throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please increment the version inside of extension.yaml.\n`);
310
+ throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please increment the version inside of extension.yaml.\n`, { exit: 103 });
311
311
  }
312
312
  const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
313
313
  let packageUri;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.functionFromEndpoint = exports.endpointFromFunction = exports.functionFromSpec = exports.specFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
3
+ exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
4
4
  const clc = require("cli-color");
5
5
  const error_1 = require("../error");
6
6
  const logger_1 = require("../logger");
@@ -214,73 +214,6 @@ async function listAllFunctions(projectId) {
214
214
  return list(projectId, "-");
215
215
  }
216
216
  exports.listAllFunctions = listAllFunctions;
217
- function specFromFunction(gcfFunction) {
218
- var _a;
219
- const [, project, , region, , id] = gcfFunction.name.split("/");
220
- let trigger;
221
- let uri;
222
- if (gcfFunction.httpsTrigger) {
223
- trigger = {};
224
- uri = gcfFunction.httpsTrigger.url;
225
- }
226
- else {
227
- trigger = {
228
- eventType: gcfFunction.eventTrigger.eventType,
229
- eventFilters: {
230
- resource: gcfFunction.eventTrigger.resource,
231
- },
232
- retry: !!((_a = gcfFunction.eventTrigger.failurePolicy) === null || _a === void 0 ? void 0 : _a.retry),
233
- };
234
- }
235
- if (!runtimes.isValidRuntime(gcfFunction.runtime)) {
236
- logger_1.logger.debug("GCFv1 function has a deprecated runtime:", JSON.stringify(gcfFunction, null, 2));
237
- }
238
- const cloudFunction = {
239
- platform: "gcfv1",
240
- id,
241
- project,
242
- region,
243
- trigger,
244
- entryPoint: gcfFunction.entryPoint,
245
- runtime: gcfFunction.runtime,
246
- };
247
- if (uri) {
248
- cloudFunction.uri = uri;
249
- }
250
- proto.copyIfPresent(cloudFunction, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "timeout", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "labels", "environmentVariables", "sourceUploadUrl");
251
- return cloudFunction;
252
- }
253
- exports.specFromFunction = specFromFunction;
254
- function functionFromSpec(cloudFunction, sourceUploadUrl) {
255
- if (cloudFunction.platform != "gcfv1") {
256
- throw new error_1.FirebaseError("Trying to create a v1 CloudFunction with v2 API. This should never happen");
257
- }
258
- if (!runtimes.isValidRuntime(cloudFunction.runtime)) {
259
- throw new error_1.FirebaseError("Failed internal assertion. Trying to deploy a new function with a deprecated runtime." +
260
- " This should never happen");
261
- }
262
- const gcfFunction = {
263
- name: backend.functionName(cloudFunction),
264
- sourceUploadUrl: sourceUploadUrl,
265
- entryPoint: cloudFunction.entryPoint,
266
- runtime: cloudFunction.runtime,
267
- };
268
- if (backend.isEventTrigger(cloudFunction.trigger)) {
269
- gcfFunction.eventTrigger = {
270
- eventType: cloudFunction.trigger.eventType,
271
- resource: cloudFunction.trigger.eventFilters.resource,
272
- };
273
- gcfFunction.eventTrigger.failurePolicy = cloudFunction.trigger.retry
274
- ? { retry: {} }
275
- : undefined;
276
- }
277
- else {
278
- gcfFunction.httpsTrigger = {};
279
- }
280
- proto.copyIfPresent(gcfFunction, cloudFunction, "serviceAccountEmail", "timeout", "availableMemoryMb", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "labels", "environmentVariables");
281
- return gcfFunction;
282
- }
283
- exports.functionFromSpec = functionFromSpec;
284
217
  function endpointFromFunction(gcfFunction) {
285
218
  var _a, _b;
286
219
  const [, project, , region, , id] = gcfFunction.name.split("/");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.endpointFromFunction = exports.functionFromEndpoint = exports.specFromFunction = exports.functionFromSpec = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.PUBSUB_PUBLISH_EVENT = exports.API_VERSION = void 0;
3
+ exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.PUBSUB_PUBLISH_EVENT = exports.API_VERSION = void 0;
4
4
  const clc = require("cli-color");
5
5
  const apiv2_1 = require("../apiv2");
6
6
  const error_1 = require("../error");
@@ -115,99 +115,6 @@ async function deleteFunction(cloudFunction) {
115
115
  }
116
116
  }
117
117
  exports.deleteFunction = deleteFunction;
118
- function functionFromSpec(cloudFunction, source) {
119
- if (cloudFunction.platform != "gcfv2") {
120
- throw new error_1.FirebaseError("Trying to create a v2 CloudFunction with v1 API. This should never happen");
121
- }
122
- if (!runtimes.isValidRuntime(cloudFunction.runtime)) {
123
- throw new error_1.FirebaseError("Failed internal assertion. Trying to deploy a new function with a deprecated runtime." +
124
- " This should never happen");
125
- }
126
- const gcfFunction = {
127
- name: backend.functionName(cloudFunction),
128
- buildConfig: {
129
- runtime: cloudFunction.runtime,
130
- entryPoint: cloudFunction.entryPoint,
131
- source: {
132
- storageSource: source,
133
- },
134
- environmentVariables: {},
135
- },
136
- serviceConfig: {},
137
- };
138
- proto.copyIfPresent(gcfFunction.serviceConfig, cloudFunction, "availableMemoryMb", "environmentVariables", "vpcConnector", "vpcConnectorEgressSettings", "serviceAccountEmail", "ingressSettings");
139
- proto.renameIfPresent(gcfFunction.serviceConfig, cloudFunction, "timeoutSeconds", "timeout", proto.secondsFromDuration);
140
- proto.renameIfPresent(gcfFunction.serviceConfig, cloudFunction, "minInstanceCount", "minInstances");
141
- proto.renameIfPresent(gcfFunction.serviceConfig, cloudFunction, "maxInstanceCount", "maxInstances");
142
- if (backend.isEventTrigger(cloudFunction.trigger)) {
143
- gcfFunction.eventTrigger = {
144
- eventType: cloudFunction.trigger.eventType,
145
- };
146
- if (cloudFunction.trigger.region) {
147
- gcfFunction.eventTrigger.triggerRegion = cloudFunction.trigger.region;
148
- }
149
- if (gcfFunction.eventTrigger.eventType === exports.PUBSUB_PUBLISH_EVENT) {
150
- gcfFunction.eventTrigger.pubsubTopic = cloudFunction.trigger.eventFilters.resource;
151
- }
152
- else {
153
- gcfFunction.eventTrigger.eventFilters = [];
154
- for (const [attribute, value] of Object.entries(cloudFunction.trigger.eventFilters)) {
155
- gcfFunction.eventTrigger.eventFilters.push({ attribute, value });
156
- }
157
- }
158
- if (cloudFunction.trigger.retry) {
159
- logger_1.logger.warn("Cannot set a retry policy on Cloud Function", cloudFunction.id);
160
- }
161
- }
162
- proto.copyIfPresent(gcfFunction, cloudFunction, "labels");
163
- return gcfFunction;
164
- }
165
- exports.functionFromSpec = functionFromSpec;
166
- function specFromFunction(gcfFunction) {
167
- const [, project, , region, , id] = gcfFunction.name.split("/");
168
- let trigger;
169
- if (gcfFunction.eventTrigger) {
170
- trigger = {
171
- eventType: gcfFunction.eventTrigger.eventType,
172
- eventFilters: {},
173
- retry: false,
174
- };
175
- if (gcfFunction.eventTrigger.triggerRegion) {
176
- trigger.region = gcfFunction.eventTrigger.triggerRegion;
177
- }
178
- if (gcfFunction.eventTrigger.pubsubTopic) {
179
- trigger.eventFilters.resource = gcfFunction.eventTrigger.pubsubTopic;
180
- }
181
- else {
182
- for (const { attribute, value } of gcfFunction.eventTrigger.eventFilters || []) {
183
- trigger.eventFilters[attribute] = value;
184
- }
185
- }
186
- }
187
- else {
188
- trigger = {};
189
- }
190
- if (!runtimes.isValidRuntime(gcfFunction.buildConfig.runtime)) {
191
- logger_1.logger.debug("GCFv2 function has a deprecated runtime:", JSON.stringify(gcfFunction, null, 2));
192
- }
193
- const cloudFunction = {
194
- platform: "gcfv2",
195
- id,
196
- project,
197
- region,
198
- trigger,
199
- entryPoint: gcfFunction.buildConfig.entryPoint,
200
- runtime: gcfFunction.buildConfig.runtime,
201
- uri: gcfFunction.serviceConfig.uri,
202
- };
203
- proto.copyIfPresent(cloudFunction, gcfFunction.serviceConfig, "serviceAccountEmail", "availableMemoryMb", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables");
204
- proto.renameIfPresent(cloudFunction, gcfFunction.serviceConfig, "timeout", "timeoutSeconds", proto.durationFromSeconds);
205
- proto.renameIfPresent(cloudFunction, gcfFunction.serviceConfig, "minInstances", "minInstanceCount");
206
- proto.renameIfPresent(cloudFunction, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount");
207
- proto.copyIfPresent(cloudFunction, gcfFunction, "labels");
208
- return cloudFunction;
209
- }
210
- exports.specFromFunction = specFromFunction;
211
118
  function functionFromEndpoint(endpoint, source) {
212
119
  if (endpoint.platform != "gcfv2") {
213
120
  throw new error_1.FirebaseError("Trying to create a v2 CloudFunction with v1 API. This should never happen");
@@ -281,6 +188,7 @@ function endpointFromFunction(gcfFunction) {
281
188
  trigger.eventTrigger.eventFilters[attribute] = value;
282
189
  }
283
190
  }
191
+ proto.renameIfPresent(trigger.eventTrigger, gcfFunction.eventTrigger, "region", "triggerRegion");
284
192
  }
285
193
  else {
286
194
  trigger = { httpsTrigger: {} };