firebase-tools 9.18.0 → 9.22.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 (114) hide show
  1. package/CHANGELOG.md +3 -6
  2. package/lib/api.js +3 -0
  3. package/lib/apiv2.js +8 -5
  4. package/lib/command.js +1 -1
  5. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  6. package/lib/commands/deploy.js +9 -1
  7. package/lib/commands/ext-configure.js +9 -2
  8. package/lib/commands/ext-dev-deprecate.js +63 -0
  9. package/lib/commands/ext-dev-extension-delete.js +2 -1
  10. package/lib/commands/ext-dev-publish.js +10 -4
  11. package/lib/commands/ext-dev-undeprecate.js +56 -0
  12. package/lib/commands/ext-dev-unpublish.js +12 -4
  13. package/lib/commands/ext-export.js +44 -0
  14. package/lib/commands/ext-install.js +50 -13
  15. package/lib/commands/ext-uninstall.js +6 -0
  16. package/lib/commands/ext-update.js +60 -18
  17. package/lib/commands/functions-config-export.js +115 -0
  18. package/lib/commands/functions-delete.js +47 -25
  19. package/lib/commands/functions-list.js +12 -12
  20. package/lib/commands/index.js +9 -0
  21. package/lib/commands/init.js +3 -0
  22. package/lib/config.js +3 -2
  23. package/lib/deploy/extensions/args.js +2 -0
  24. package/lib/deploy/extensions/deploy.js +49 -0
  25. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  26. package/lib/deploy/extensions/errors.js +31 -0
  27. package/lib/deploy/extensions/index.js +8 -0
  28. package/lib/deploy/extensions/planner.js +95 -0
  29. package/lib/deploy/extensions/prepare.js +103 -0
  30. package/lib/deploy/extensions/release.js +43 -0
  31. package/lib/deploy/extensions/secrets.js +150 -0
  32. package/lib/deploy/extensions/tasks.js +98 -0
  33. package/lib/deploy/extensions/validate.js +17 -0
  34. package/lib/deploy/functions/backend.js +93 -115
  35. package/lib/deploy/functions/checkIam.js +8 -8
  36. package/lib/deploy/functions/containerCleaner.js +82 -22
  37. package/lib/deploy/functions/deploy.js +4 -10
  38. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  39. package/lib/deploy/functions/prepare.js +62 -27
  40. package/lib/deploy/functions/pricing.js +17 -17
  41. package/lib/deploy/functions/prompts.js +22 -21
  42. package/lib/deploy/functions/release/executor.js +39 -0
  43. package/lib/deploy/functions/release/fabricator.js +422 -0
  44. package/lib/deploy/functions/release/index.js +73 -0
  45. package/lib/deploy/functions/release/planner.js +162 -0
  46. package/lib/deploy/functions/release/reporter.js +165 -0
  47. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  48. package/lib/deploy/functions/release/timer.js +14 -0
  49. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
  50. package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
  51. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  52. package/lib/deploy/functions/validate.js +1 -24
  53. package/lib/deploy/index.js +1 -0
  54. package/lib/downloadUtils.js +37 -0
  55. package/lib/emulator/auth/apiSpec.js +1788 -403
  56. package/lib/emulator/auth/handlers.js +6 -5
  57. package/lib/emulator/auth/operations.js +439 -40
  58. package/lib/emulator/auth/server.js +32 -11
  59. package/lib/emulator/auth/state.js +205 -5
  60. package/lib/emulator/auth/widget_ui.js +2 -2
  61. package/lib/emulator/download.js +2 -31
  62. package/lib/emulator/downloadableEmulators.js +7 -7
  63. package/lib/emulator/emulatorLogger.js +0 -3
  64. package/lib/emulator/events/types.js +16 -0
  65. package/lib/emulator/functionsEmulator.js +120 -21
  66. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  67. package/lib/emulator/functionsEmulatorShared.js +51 -7
  68. package/lib/emulator/functionsEmulatorShell.js +1 -1
  69. package/lib/emulator/pubsubEmulator.js +61 -40
  70. package/lib/emulator/storage/cloudFunctions.js +37 -7
  71. package/lib/extensions/askUserForConsent.js +14 -1
  72. package/lib/extensions/askUserForParam.js +81 -4
  73. package/lib/extensions/billingMigrationHelper.js +1 -11
  74. package/lib/extensions/changelog.js +2 -1
  75. package/lib/extensions/checkProjectBilling.js +7 -7
  76. package/lib/extensions/displayExtensionInfo.js +35 -33
  77. package/lib/extensions/emulator/optionsHelper.js +3 -3
  78. package/lib/extensions/emulator/triggerHelper.js +2 -32
  79. package/lib/extensions/export.js +107 -0
  80. package/lib/extensions/extensionsApi.js +149 -97
  81. package/lib/extensions/extensionsHelper.js +36 -32
  82. package/lib/extensions/listExtensions.js +16 -11
  83. package/lib/extensions/paramHelper.js +73 -40
  84. package/lib/extensions/provisioningHelper.js +16 -3
  85. package/lib/extensions/refs.js +67 -0
  86. package/lib/extensions/secretsUtils.js +59 -0
  87. package/lib/extensions/updateHelper.js +33 -47
  88. package/lib/extensions/versionHelper.js +14 -0
  89. package/lib/extensions/warnings.js +33 -1
  90. package/lib/functional.js +64 -0
  91. package/lib/functions/env.js +26 -13
  92. package/lib/functions/runtimeConfigExport.js +137 -0
  93. package/lib/gcp/artifactregistry.js +16 -0
  94. package/lib/gcp/cloudfunctions.js +65 -35
  95. package/lib/gcp/cloudfunctionsv2.js +56 -43
  96. package/lib/gcp/cloudscheduler.js +22 -16
  97. package/lib/gcp/cloudtasks.js +143 -0
  98. package/lib/gcp/docker.js +7 -1
  99. package/lib/gcp/proto.js +2 -2
  100. package/lib/gcp/pubsub.js +1 -9
  101. package/lib/gcp/secretManager.js +132 -0
  102. package/lib/gcp/storage.js +16 -0
  103. package/lib/previews.js +1 -1
  104. package/lib/requireInteractive.js +12 -0
  105. package/lib/utils.js +30 -1
  106. package/package.json +6 -4
  107. package/schema/firebase-config.json +9 -0
  108. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  109. package/lib/deploy/functions/deploymentTimer.js +0 -23
  110. package/lib/deploy/functions/errorHandler.js +0 -75
  111. package/lib/deploy/functions/release.js +0 -116
  112. package/lib/deploy/functions/tasks.js +0 -324
  113. package/lib/functions/listFunctions.js +0 -10
  114. package/lib/functionsDelete.js +0 -60
@@ -26,6 +26,15 @@ exports.authOperations = {
26
26
  signInWithPhoneNumber,
27
27
  signUp,
28
28
  update: setAccountInfo,
29
+ mfaEnrollment: {
30
+ finalize: mfaEnrollmentFinalize,
31
+ start: mfaEnrollmentStart,
32
+ withdraw: mfaEnrollmentWithdraw,
33
+ },
34
+ mfaSignIn: {
35
+ start: mfaSignInStart,
36
+ finalize: mfaSignInFinalize,
37
+ },
29
38
  },
30
39
  projects: {
31
40
  createSessionCookie,
@@ -41,6 +50,25 @@ exports.authOperations = {
41
50
  batchDelete,
42
51
  batchGet,
43
52
  },
53
+ tenants: {
54
+ create: createTenant,
55
+ delete: deleteTenant,
56
+ get: getTenant,
57
+ list: listTenants,
58
+ patch: updateTenant,
59
+ createSessionCookie,
60
+ accounts: {
61
+ _: signUp,
62
+ batchCreate,
63
+ batchDelete,
64
+ batchGet,
65
+ delete: deleteAccount,
66
+ lookup,
67
+ query: queryAccounts,
68
+ sendOobCode,
69
+ update: setAccountInfo,
70
+ },
71
+ },
44
72
  },
45
73
  },
46
74
  securetoken: {
@@ -66,8 +94,16 @@ exports.authOperations = {
66
94
  };
67
95
  const PASSWORD_MIN_LENGTH = 6;
68
96
  exports.CUSTOM_TOKEN_AUDIENCE = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit";
97
+ const MFA_INELIGIBLE_PROVIDER = new Set([
98
+ state_1.PROVIDER_ANONYMOUS,
99
+ state_1.PROVIDER_PHONE,
100
+ state_1.PROVIDER_CUSTOM,
101
+ state_1.PROVIDER_GAME_CENTER,
102
+ ]);
69
103
  function signUp(state, reqBody, ctx) {
70
104
  var _a;
105
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
106
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
71
107
  let provider;
72
108
  const updates = {
73
109
  lastLoginAt: Date.now().toString(),
@@ -99,9 +135,11 @@ function signUp(state, reqBody, ctx) {
99
135
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
100
136
  errors_1.assert(reqBody.password, "MISSING_PASSWORD");
101
137
  provider = state_1.PROVIDER_PASSWORD;
138
+ errors_1.assert(state.allowPasswordSignup, "OPERATION_NOT_ALLOWED");
102
139
  }
103
140
  else {
104
141
  provider = state_1.PROVIDER_ANONYMOUS;
142
+ errors_1.assert(state.enableAnonymousUser, "ADMIN_ONLY_OPERATION");
105
143
  }
106
144
  }
107
145
  if (reqBody.email) {
@@ -122,6 +160,9 @@ function signUp(state, reqBody, ctx) {
122
160
  generateEnrollmentIds: true,
123
161
  });
124
162
  }
163
+ if (state instanceof state_1.TenantProjectState) {
164
+ updates.tenantId = state.tenantId;
165
+ }
125
166
  let user;
126
167
  if (reqBody.idToken) {
127
168
  ({ user } = parseIdToken(state, reqBody.idToken));
@@ -142,6 +183,7 @@ function signUp(state, reqBody, ctx) {
142
183
  }
143
184
  function lookup(state, reqBody, ctx) {
144
185
  var _a, _b, _c, _d, _e;
186
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
145
187
  const seenLocalIds = new Set();
146
188
  const users = [];
147
189
  function tryAddUser(maybeUser) {
@@ -182,6 +224,8 @@ function lookup(state, reqBody, ctx) {
182
224
  }
183
225
  function batchCreate(state, reqBody) {
184
226
  var _a, _b;
227
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
228
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
185
229
  errors_1.assert((_a = reqBody.users) === null || _a === void 0 ? void 0 : _a.length, "MISSING_USER_ACCOUNT");
186
230
  if (reqBody.sanityCheck) {
187
231
  if (state.oneAccountPerEmail) {
@@ -221,6 +265,12 @@ function batchCreate(state, reqBody) {
221
265
  photoUrl: userInfo.photoUrl,
222
266
  lastLoginAt: userInfo.lastLoginAt,
223
267
  };
268
+ if (userInfo.tenantId) {
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) {
272
+ fields.tenantId = state.tenantId;
273
+ }
224
274
  if (userInfo.passwordHash) {
225
275
  fields.passwordHash = userInfo.passwordHash;
226
276
  fields.salt = userInfo.salt;
@@ -275,6 +325,26 @@ function batchCreate(state, reqBody) {
275
325
  }
276
326
  fields.emailVerified = !!userInfo.emailVerified;
277
327
  fields.disabled = !!userInfo.disabled;
328
+ if (userInfo.mfaInfo) {
329
+ fields.mfaInfo = [];
330
+ errors_1.assert(fields.email, "Second factor account requires email to be presented.");
331
+ errors_1.assert(fields.emailVerified, "Second factor account requires email to be verified.");
332
+ const existingIds = new Set();
333
+ for (const enrollment of userInfo.mfaInfo) {
334
+ if (enrollment.mfaEnrollmentId) {
335
+ errors_1.assert(!existingIds.has(enrollment.mfaEnrollmentId), "Enrollment id already exists.");
336
+ existingIds.add(enrollment.mfaEnrollmentId);
337
+ }
338
+ }
339
+ for (const enrollment of userInfo.mfaInfo) {
340
+ enrollment.mfaEnrollmentId = enrollment.mfaEnrollmentId || newRandomId(28, existingIds);
341
+ enrollment.enrolledAt = enrollment.enrolledAt || new Date().toISOString();
342
+ errors_1.assert(enrollment.phoneInfo, "Second factor not supported.");
343
+ errors_1.assert(utils_1.isValidPhoneNumber(enrollment.phoneInfo), "Phone number format is invalid");
344
+ enrollment.unobfuscatedPhoneInfo = enrollment.phoneInfo;
345
+ fields.mfaInfo.push(enrollment);
346
+ }
347
+ }
278
348
  if (state.getUserByLocalId(userInfo.localId)) {
279
349
  errors_1.assert(reqBody.allowOverwrite, "localId belongs to an existing account - can not overwrite.");
280
350
  }
@@ -332,6 +402,7 @@ function batchDelete(state, reqBody) {
332
402
  return { errors: errors.length ? errors : undefined };
333
403
  }
334
404
  function batchGet(state, reqBody, ctx) {
405
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
335
406
  const maxResults = Math.min(Math.floor(ctx.params.query.maxResults) || 20, 1000);
336
407
  const users = state.queryUsers({}, { sortByField: "localId", order: "ASC", startToken: ctx.params.query.nextPageToken });
337
408
  let newPageToken = undefined;
@@ -349,6 +420,8 @@ function batchGet(state, reqBody, ctx) {
349
420
  }
350
421
  function createAuthUri(state, reqBody) {
351
422
  var _a;
423
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
424
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
352
425
  const sessionId = reqBody.sessionId || utils_1.randomId(27);
353
426
  if (reqBody.providerId) {
354
427
  throw new errors_1.NotImplementedError("Sign-in with IDP is not yet supported.");
@@ -408,6 +481,7 @@ function createAuthUri(state, reqBody) {
408
481
  const SESSION_COOKIE_MIN_VALID_DURATION = 5 * 60;
409
482
  exports.SESSION_COOKIE_MAX_VALID_DURATION = 14 * 24 * 60 * 60;
410
483
  function createSessionCookie(state, reqBody, ctx) {
484
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
411
485
  errors_1.assert(reqBody.idToken, "MISSING_ID_TOKEN");
412
486
  const validDuration = Number(reqBody.validDuration) || exports.SESSION_COOKIE_MAX_VALID_DURATION;
413
487
  errors_1.assert(validDuration >= SESSION_COOKIE_MIN_VALID_DURATION &&
@@ -422,6 +496,7 @@ function createSessionCookie(state, reqBody, ctx) {
422
496
  }
423
497
  function deleteAccount(state, reqBody, ctx) {
424
498
  var _a;
499
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
425
500
  let user;
426
501
  if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
427
502
  errors_1.assert(reqBody.localId, "MISSING_LOCAL_ID");
@@ -439,6 +514,8 @@ function deleteAccount(state, reqBody, ctx) {
439
514
  };
440
515
  }
441
516
  function getProjects(state) {
517
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
518
+ errors_1.assert(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
442
519
  return {
443
520
  projectId: state.projectNumber,
444
521
  authorizedDomains: [
@@ -446,7 +523,8 @@ function getProjects(state) {
446
523
  ],
447
524
  };
448
525
  }
449
- function getRecaptchaParams() {
526
+ function getRecaptchaParams(state) {
527
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
450
528
  return {
451
529
  kind: "identitytoolkit#GetRecaptchaParamResponse",
452
530
  recaptchaStoken: "This-is-a-fake-token__Dont-send-this-to-the-Recaptcha-service__The-Auth-Emulator-does-not-support-Recaptcha",
@@ -455,6 +533,7 @@ function getRecaptchaParams() {
455
533
  }
456
534
  function queryAccounts(state, reqBody) {
457
535
  var _a;
536
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
458
537
  if ((_a = reqBody.expression) === null || _a === void 0 ? void 0 : _a.length) {
459
538
  throw new errors_1.NotImplementedError("expression is not implemented.");
460
539
  }
@@ -491,6 +570,9 @@ function queryAccounts(state, reqBody) {
491
570
  }
492
571
  function resetPassword(state, reqBody) {
493
572
  var _a;
573
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
574
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
575
+ errors_1.assert(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
494
576
  errors_1.assert(reqBody.oobCode, "MISSING_OOB_CODE");
495
577
  const oob = state.validateOobCode(reqBody.oobCode);
496
578
  errors_1.assert(oob, "INVALID_OOB_CODE");
@@ -498,11 +580,11 @@ function resetPassword(state, reqBody) {
498
580
  errors_1.assert(oob.requestType === "PASSWORD_RESET", "INVALID_OOB_CODE");
499
581
  errors_1.assert(reqBody.newPassword.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters`);
500
582
  state.deleteOobCode(reqBody.oobCode);
501
- const user = state.getUserByEmail(oob.email);
583
+ let user = state.getUserByEmail(oob.email);
502
584
  errors_1.assert(user, "INVALID_OOB_CODE");
503
585
  const salt = "fakeSalt" + utils_1.randomId(20);
504
586
  const passwordHash = hashPassword(reqBody.newPassword, salt);
505
- state.updateUserByLocalId(user.localId, {
587
+ user = state.updateUserByLocalId(user.localId, {
506
588
  emailVerified: true,
507
589
  passwordHash,
508
590
  salt,
@@ -519,6 +601,8 @@ function resetPassword(state, reqBody) {
519
601
  exports.resetPassword = resetPassword;
520
602
  function sendOobCode(state, reqBody, ctx) {
521
603
  var _a;
604
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
605
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
522
606
  errors_1.assert(reqBody.requestType && reqBody.requestType !== "OOB_REQ_TYPE_UNSPECIFIED", "MISSING_REQ_TYPE");
523
607
  if (reqBody.returnOobLink) {
524
608
  errors_1.assert((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2, "INSUFFICIENT_PERMISSION");
@@ -530,6 +614,7 @@ function sendOobCode(state, reqBody, ctx) {
530
614
  let mode;
531
615
  switch (reqBody.requestType) {
532
616
  case "EMAIL_SIGNIN":
617
+ errors_1.assert(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
533
618
  mode = "signIn";
534
619
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
535
620
  email = utils_1.canonicalizeEmailAddress(reqBody.email);
@@ -583,7 +668,13 @@ function sendOobCode(state, reqBody, ctx) {
583
668
  }
584
669
  }
585
670
  function sendVerificationCode(state, reqBody) {
671
+ var _a;
672
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
673
+ errors_1.assert(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
674
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
586
675
  errors_1.assert(reqBody.phoneNumber && utils_1.isValidPhoneNumber(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
676
+ const user = state.getUserByPhoneNumber(reqBody.phoneNumber);
677
+ errors_1.assert(!((_a = user === null || user === void 0 ? void 0 : user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length), "UNSUPPORTED_FIRST_FACTOR : A phone number cannot be set as a first factor on an SMS based MFA user.");
587
678
  const { sessionInfo, phoneNumber, code } = state.createVerificationCode(reqBody.phoneNumber);
588
679
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To verify the phone number ${phoneNumber}, use the code ${code}.`);
589
680
  return {
@@ -592,6 +683,8 @@ function sendVerificationCode(state, reqBody) {
592
683
  }
593
684
  function setAccountInfo(state, reqBody, ctx) {
594
685
  var _a;
686
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
687
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
595
688
  const url = utils_1.authEmulatorUrl(ctx.req);
596
689
  return setAccountInfoImpl(state, reqBody, {
597
690
  privileged: !!((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2),
@@ -603,8 +696,6 @@ function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl =
603
696
  const unimplementedFields = [
604
697
  "provider",
605
698
  "upgradeToFederatedLogin",
606
- "captchaChallenge",
607
- "captchaResponse",
608
699
  "linkProviderUserInfo",
609
700
  ];
610
701
  for (const field of unimplementedFields) {
@@ -787,6 +878,9 @@ function createOobRecord(state, email, url, params) {
787
878
  if (params.continueUrl) {
788
879
  url.searchParams.set("continueUrl", params.continueUrl);
789
880
  }
881
+ if (state instanceof state_1.TenantProjectState) {
882
+ url.searchParams.set("tenantId", state.tenantId);
883
+ }
790
884
  return url.toString();
791
885
  });
792
886
  return oobRecord;
@@ -815,6 +909,7 @@ function logOobMessage(oobRecord) {
815
909
  }
816
910
  function signInWithCustomToken(state, reqBody) {
817
911
  var _a;
912
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
818
913
  errors_1.assert(reqBody.token, "MISSING_CUSTOM_TOKEN");
819
914
  let payload;
820
915
  if (reqBody.token.startsWith("{")) {
@@ -827,6 +922,9 @@ function signInWithCustomToken(state, reqBody) {
827
922
  }
828
923
  else {
829
924
  const decoded = jsonwebtoken_1.decode(reqBody.token, { complete: true });
925
+ if (state instanceof state_1.TenantProjectState) {
926
+ errors_1.assert((decoded === null || decoded === void 0 ? void 0 : decoded.payload.tenant_id) === state.tenantId, "TENANT_ID_MISMATCH");
927
+ }
830
928
  errors_1.assert(decoded, "INVALID_CUSTOM_TOKEN : Invalid assertion format");
831
929
  if (decoded.header.alg !== "none") {
832
930
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("WARN", "Received a signed custom token. Auth Emulator does not validate JWTs and IS NOT SECURE");
@@ -837,16 +935,17 @@ function signInWithCustomToken(state, reqBody) {
837
935
  }
838
936
  const localId = (_a = coercePrimitiveToString(payload.uid)) !== null && _a !== void 0 ? _a : coercePrimitiveToString(payload.user_id);
839
937
  errors_1.assert(localId, "MISSING_IDENTIFIER");
840
- let claims = {};
938
+ let extraClaims = {};
841
939
  if ("claims" in payload) {
842
940
  validateCustomClaims(payload.claims);
843
- claims = payload.claims;
941
+ extraClaims = payload.claims;
844
942
  }
845
943
  let user = state.getUserByLocalId(localId);
846
- const isNewUser = !user;
944
+ const isNewUser = state.usageMode === state_1.UsageMode.PASSTHROUGH ? false : !user;
847
945
  const updates = {
848
946
  customAuth: true,
849
947
  lastLoginAt: Date.now().toString(),
948
+ tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined,
850
949
  };
851
950
  if (user) {
852
951
  errors_1.assert(!user.disabled, "USER_DISABLED");
@@ -858,12 +957,13 @@ function signInWithCustomToken(state, reqBody) {
858
957
  throw new Error(`Internal assertion error: trying to create duplicate localId: ${localId}`);
859
958
  }
860
959
  }
861
- if (user.mfaInfo) {
862
- throw new errors_1.NotImplementedError("MFA Login not yet implemented.");
863
- }
864
- return Object.assign({ kind: "identitytoolkit#VerifyCustomTokenResponse", isNewUser }, issueTokens(state, user, state_1.PROVIDER_CUSTOM, claims));
960
+ return Object.assign({ kind: "identitytoolkit#VerifyCustomTokenResponse", isNewUser }, issueTokens(state, user, state_1.PROVIDER_CUSTOM, { extraClaims }));
865
961
  }
866
962
  function signInWithEmailLink(state, reqBody) {
963
+ var _a;
964
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
965
+ errors_1.assert(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
966
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
867
967
  const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
868
968
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
869
969
  const email = utils_1.canonicalizeEmailAddress(reqBody.email);
@@ -876,8 +976,10 @@ function signInWithEmailLink(state, reqBody) {
876
976
  email,
877
977
  emailVerified: true,
878
978
  emailLinkSignin: true,
879
- lastLoginAt: Date.now().toString(),
880
979
  };
980
+ if (state instanceof state_1.TenantProjectState) {
981
+ updates.tenantId = state.tenantId;
982
+ }
881
983
  let user = state.getUserByEmail(email);
882
984
  const isNewUser = !user && !userFromIdToken;
883
985
  if (!user) {
@@ -893,14 +995,24 @@ function signInWithEmailLink(state, reqBody) {
893
995
  errors_1.assert(!userFromIdToken || userFromIdToken.localId === user.localId, "EMAIL_EXISTS");
894
996
  user = state.updateUserByLocalId(user.localId, updates);
895
997
  }
896
- if (user.mfaInfo) {
897
- throw new errors_1.NotImplementedError("MFA Login not yet implemented.");
998
+ const response = {
999
+ kind: "identitytoolkit#EmailLinkSigninResponse",
1000
+ email,
1001
+ localId: user.localId,
1002
+ isNewUser,
1003
+ };
1004
+ if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length)) {
1005
+ return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
1006
+ }
1007
+ else {
1008
+ user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
1009
+ return Object.assign(Object.assign({}, response), issueTokens(state, user, state_1.PROVIDER_PASSWORD));
898
1010
  }
899
- const tokens = issueTokens(state, user, state_1.PROVIDER_PASSWORD);
900
- return Object.assign({ kind: "identitytoolkit#EmailLinkSigninResponse", email, localId: user.localId, isNewUser }, tokens);
901
1011
  }
902
1012
  function signInWithIdp(state, reqBody) {
903
- var _a;
1013
+ var _a, _b;
1014
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1015
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
904
1016
  if (reqBody.returnRefreshToken) {
905
1017
  throw new errors_1.NotImplementedError("returnRefreshToken is not implemented yet.");
906
1018
  }
@@ -972,27 +1084,36 @@ function signInWithIdp(state, reqBody) {
972
1084
  };
973
1085
  let user;
974
1086
  if (response.isNewUser) {
975
- user = state.createUser(Object.assign(Object.assign({}, accountUpdates.fields), { lastLoginAt: Date.now().toString(), providerUserInfo: [providerUserInfo] }));
1087
+ user = state.createUser(Object.assign(Object.assign({}, accountUpdates.fields), { lastLoginAt: Date.now().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined }));
976
1088
  response.localId = user.localId;
977
1089
  }
978
1090
  else {
979
1091
  if (!response.localId) {
980
1092
  throw new Error("Internal assertion error: localId not set for exising user.");
981
1093
  }
982
- user = state.updateUserByLocalId(response.localId, Object.assign(Object.assign({}, accountUpdates.fields), { lastLoginAt: Date.now().toString() }), {
1094
+ user = state.updateUserByLocalId(response.localId, Object.assign({}, accountUpdates.fields), {
983
1095
  upsertProviders: [providerUserInfo],
984
1096
  });
985
1097
  }
986
- if (user.mfaInfo) {
987
- throw new errors_1.NotImplementedError("MFA Login not yet implemented.");
988
- }
989
1098
  if (user.email === response.email) {
990
1099
  response.emailVerified = user.emailVerified;
991
1100
  }
992
- Object.assign(response, issueTokens(state, user, providerId));
993
- return response;
1101
+ if (state instanceof state_1.TenantProjectState) {
1102
+ response.tenantId = state.tenantId;
1103
+ }
1104
+ if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.length)) {
1105
+ return Object.assign(Object.assign({}, response), mfaPending(state, user, providerId));
1106
+ }
1107
+ else {
1108
+ user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
1109
+ return Object.assign(Object.assign({}, response), issueTokens(state, user, providerId));
1110
+ }
994
1111
  }
995
1112
  function signInWithPassword(state, reqBody) {
1113
+ var _a;
1114
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1115
+ errors_1.assert(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
1116
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
996
1117
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
997
1118
  errors_1.assert(reqBody.password, "MISSING_PASSWORD");
998
1119
  if (reqBody.captchaResponse || reqBody.captchaChallenge) {
@@ -1007,14 +1128,25 @@ function signInWithPassword(state, reqBody) {
1007
1128
  errors_1.assert(!user.disabled, "USER_DISABLED");
1008
1129
  errors_1.assert(user.passwordHash && user.salt, "INVALID_PASSWORD");
1009
1130
  errors_1.assert(user.passwordHash === hashPassword(reqBody.password, user.salt), "INVALID_PASSWORD");
1010
- if (user.mfaInfo) {
1011
- throw new errors_1.NotImplementedError("MFA Login not yet implemented.");
1131
+ const response = {
1132
+ kind: "identitytoolkit#VerifyPasswordResponse",
1133
+ registered: true,
1134
+ localId: user.localId,
1135
+ email,
1136
+ };
1137
+ if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length)) {
1138
+ return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
1139
+ }
1140
+ else {
1141
+ user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
1142
+ return Object.assign(Object.assign({}, response), issueTokens(state, user, state_1.PROVIDER_PASSWORD));
1012
1143
  }
1013
- user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
1014
- const tokens = issueTokens(state, user, state_1.PROVIDER_PASSWORD);
1015
- return Object.assign({ kind: "identitytoolkit#VerifyPasswordResponse", registered: true, localId: user.localId, email, displayName: user.displayName, profilePicture: user.photoUrl }, tokens);
1016
1144
  }
1017
1145
  function signInWithPhoneNumber(state, reqBody) {
1146
+ var _a;
1147
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1148
+ errors_1.assert(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
1149
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
1018
1150
  let phoneNumber;
1019
1151
  if (reqBody.temporaryProof) {
1020
1152
  errors_1.assert(reqBody.phoneNumber, "MISSING_PHONE_NUMBER");
@@ -1036,6 +1168,7 @@ function signInWithPhoneNumber(state, reqBody) {
1036
1168
  const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
1037
1169
  if (!user) {
1038
1170
  if (userFromIdToken) {
1171
+ errors_1.assert(!((_a = userFromIdToken.mfaInfo) === null || _a === void 0 ? void 0 : _a.length), "UNSUPPORTED_FIRST_FACTOR : A phone number cannot be set as a first factor on an SMS based MFA user.");
1039
1172
  user = state.updateUserByLocalId(userFromIdToken.localId, updates);
1040
1173
  }
1041
1174
  else {
@@ -1053,21 +1186,22 @@ function signInWithPhoneNumber(state, reqBody) {
1053
1186
  }
1054
1187
  user = state.updateUserByLocalId(user.localId, updates);
1055
1188
  }
1056
- if (user.mfaInfo) {
1057
- throw new errors_1.NotImplementedError("MFA Login not yet implemented.");
1058
- }
1059
1189
  const tokens = issueTokens(state, user, state_1.PROVIDER_PHONE);
1060
1190
  return Object.assign({ isNewUser,
1061
1191
  phoneNumber, localId: user.localId }, tokens);
1062
1192
  }
1063
1193
  function grantToken(state, reqBody) {
1194
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
1064
1195
  errors_1.assert(reqBody.grantType, "MISSING_GRANT_TYPE");
1065
1196
  errors_1.assert(reqBody.grantType === "refresh_token", "INVALID_GRANT_TYPE");
1066
1197
  errors_1.assert(reqBody.refreshToken, "MISSING_REFRESH_TOKEN");
1067
1198
  const refreshTokenRecord = state.validateRefreshToken(reqBody.refreshToken);
1068
1199
  errors_1.assert(refreshTokenRecord, "INVALID_REFRESH_TOKEN");
1069
1200
  errors_1.assert(!refreshTokenRecord.user.disabled, "USER_DISABLED");
1070
- const tokens = issueTokens(state, refreshTokenRecord.user, refreshTokenRecord.provider, refreshTokenRecord.extraClaims);
1201
+ const tokens = issueTokens(state, refreshTokenRecord.user, refreshTokenRecord.provider, {
1202
+ extraClaims: refreshTokenRecord.extraClaims,
1203
+ secondFactor: refreshTokenRecord.secondFactor,
1204
+ });
1071
1205
  return {
1072
1206
  id_token: tokens.idToken,
1073
1207
  access_token: tokens.idToken,
@@ -1087,14 +1221,31 @@ function getEmulatorProjectConfig(state) {
1087
1221
  signIn: {
1088
1222
  allowDuplicateEmails: !state.oneAccountPerEmail,
1089
1223
  },
1224
+ usageMode: state.usageMode,
1090
1225
  };
1091
1226
  }
1092
1227
  function updateEmulatorProjectConfig(state, reqBody) {
1093
1228
  var _a;
1094
1229
  const allowDuplicateEmails = (_a = reqBody.signIn) === null || _a === void 0 ? void 0 : _a.allowDuplicateEmails;
1095
1230
  if (allowDuplicateEmails != null) {
1231
+ errors_1.assert(state instanceof state_1.AgentProjectState, "((Only top level projects can set oneAccountPerEmail.))");
1096
1232
  state.oneAccountPerEmail = !allowDuplicateEmails;
1097
1233
  }
1234
+ const usageMode = reqBody.usageMode;
1235
+ if (usageMode != null) {
1236
+ errors_1.assert(state instanceof state_1.AgentProjectState, "((Only top level projects can set usageMode.))");
1237
+ switch (usageMode) {
1238
+ case "PASSTHROUGH":
1239
+ errors_1.assert(state.getUserCount() === 0, "Users are present, unable to set passthrough mode");
1240
+ state.usageMode = state_1.UsageMode.PASSTHROUGH;
1241
+ break;
1242
+ case "DEFAULT":
1243
+ state.usageMode = state_1.UsageMode.DEFAULT;
1244
+ break;
1245
+ default:
1246
+ throw new errors_1.BadRequestError("Invalid usage mode provided");
1247
+ }
1248
+ }
1098
1249
  return getEmulatorProjectConfig(state);
1099
1250
  }
1100
1251
  function listOobCodesInProject(state) {
@@ -1107,6 +1258,122 @@ function listVerificationCodesInProject(state) {
1107
1258
  verificationCodes: [...state.listVerificationCodes()],
1108
1259
  };
1109
1260
  }
1261
+ function mfaEnrollmentStart(state, reqBody) {
1262
+ var _a, _b;
1263
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1264
+ errors_1.assert((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
1265
+ errors_1.assert(reqBody.idToken, "MISSING_ID_TOKEN");
1266
+ const { user, signInProvider } = parseIdToken(state, reqBody.idToken);
1267
+ errors_1.assert(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
1268
+ errors_1.assert(user.emailVerified, "UNVERIFIED_EMAIL : Need to verify email first before enrolling second factors.");
1269
+ errors_1.assert(reqBody.phoneEnrollmentInfo, "INVALID_ARGUMENT : ((Missing phoneEnrollmentInfo.))");
1270
+ const phoneNumber = reqBody.phoneEnrollmentInfo.phoneNumber;
1271
+ errors_1.assert(phoneNumber && utils_1.isValidPhoneNumber(phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
1272
+ errors_1.assert(!((_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.some((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber)), "SECOND_FACTOR_EXISTS : Phone number already enrolled as second factor for this account.");
1273
+ const { sessionInfo, code } = state.createVerificationCode(phoneNumber);
1274
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To enroll MFA with ${phoneNumber}, use the code ${code}.`);
1275
+ return {
1276
+ phoneSessionInfo: {
1277
+ sessionInfo,
1278
+ },
1279
+ };
1280
+ }
1281
+ function mfaEnrollmentFinalize(state, reqBody) {
1282
+ var _a, _b;
1283
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1284
+ errors_1.assert((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
1285
+ errors_1.assert(reqBody.idToken, "MISSING_ID_TOKEN");
1286
+ let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
1287
+ errors_1.assert(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
1288
+ errors_1.assert(reqBody.phoneVerificationInfo, "INVALID_ARGUMENT : ((Missing phoneVerificationInfo.))");
1289
+ if (reqBody.phoneVerificationInfo.androidVerificationProof) {
1290
+ throw new errors_1.NotImplementedError("androidVerificationProof is unsupported!");
1291
+ }
1292
+ const { code, sessionInfo } = reqBody.phoneVerificationInfo;
1293
+ errors_1.assert(code, "MISSING_CODE");
1294
+ errors_1.assert(sessionInfo, "MISSING_SESSION_INFO");
1295
+ const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
1296
+ errors_1.assert(!((_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.some((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber)), "SECOND_FACTOR_EXISTS : Phone number already enrolled as second factor for this account.");
1297
+ const existingFactors = user.mfaInfo || [];
1298
+ const existingIds = new Set();
1299
+ for (const { mfaEnrollmentId } of existingFactors) {
1300
+ if (mfaEnrollmentId) {
1301
+ existingIds.add(mfaEnrollmentId);
1302
+ }
1303
+ }
1304
+ const enrollment = {
1305
+ displayName: reqBody.displayName,
1306
+ enrolledAt: new Date().toISOString(),
1307
+ mfaEnrollmentId: newRandomId(28, existingIds),
1308
+ phoneInfo: phoneNumber,
1309
+ unobfuscatedPhoneInfo: phoneNumber,
1310
+ };
1311
+ user = state.updateUserByLocalId(user.localId, {
1312
+ mfaInfo: [...existingFactors, enrollment],
1313
+ });
1314
+ const { idToken, refreshToken } = issueTokens(state, user, signInProvider, {
1315
+ secondFactor: { identifier: enrollment.mfaEnrollmentId, provider: state_1.PROVIDER_PHONE },
1316
+ });
1317
+ return {
1318
+ idToken,
1319
+ refreshToken,
1320
+ };
1321
+ }
1322
+ function mfaEnrollmentWithdraw(state, reqBody) {
1323
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1324
+ errors_1.assert(reqBody.idToken, "MISSING_ID_TOKEN");
1325
+ let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
1326
+ errors_1.assert(user.mfaInfo, "MFA_ENROLLMENT_NOT_FOUND");
1327
+ const updatedList = user.mfaInfo.filter((enrollment) => enrollment.mfaEnrollmentId !== reqBody.mfaEnrollmentId);
1328
+ errors_1.assert(updatedList.length < user.mfaInfo.length, "MFA_ENROLLMENT_NOT_FOUND");
1329
+ user = state.updateUserByLocalId(user.localId, { mfaInfo: updatedList });
1330
+ return Object.assign({}, issueTokens(state, user, signInProvider));
1331
+ }
1332
+ function mfaSignInStart(state, reqBody) {
1333
+ var _a, _b;
1334
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1335
+ errors_1.assert((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
1336
+ errors_1.assert(reqBody.mfaPendingCredential, "MISSING_MFA_PENDING_CREDENTIAL : Request does not have MFA pending credential.");
1337
+ errors_1.assert(reqBody.mfaEnrollmentId, "MISSING_MFA_ENROLLMENT_ID : No second factor identifier is provided.");
1338
+ const { user } = parsePendingCredential(state, reqBody.mfaPendingCredential);
1339
+ const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((factor) => factor.mfaEnrollmentId === reqBody.mfaEnrollmentId);
1340
+ errors_1.assert(enrollment, "MFA_ENROLLMENT_NOT_FOUND");
1341
+ const phoneNumber = enrollment.unobfuscatedPhoneInfo;
1342
+ errors_1.assert(phoneNumber, "INVALID_ARGUMENT : MFA provider not supported!");
1343
+ const { sessionInfo, code } = state.createVerificationCode(phoneNumber);
1344
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To sign in with MFA using ${phoneNumber}, use the code ${code}.`);
1345
+ return {
1346
+ phoneResponseInfo: {
1347
+ sessionInfo,
1348
+ },
1349
+ };
1350
+ }
1351
+ function mfaSignInFinalize(state, reqBody) {
1352
+ var _a, _b;
1353
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1354
+ errors_1.assert((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
1355
+ errors_1.assert(reqBody.mfaPendingCredential, "MISSING_CREDENTIAL : Please set MFA Pending Credential.");
1356
+ errors_1.assert(reqBody.phoneVerificationInfo, "INVALID_ARGUMENT : MFA provider not supported!");
1357
+ if (reqBody.phoneVerificationInfo.androidVerificationProof) {
1358
+ throw new errors_1.NotImplementedError("androidVerificationProof is unsupported!");
1359
+ }
1360
+ const { code, sessionInfo } = reqBody.phoneVerificationInfo;
1361
+ errors_1.assert(code, "MISSING_CODE");
1362
+ errors_1.assert(sessionInfo, "MISSING_SESSION_INFO");
1363
+ const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
1364
+ let { user, signInProvider } = parsePendingCredential(state, reqBody.mfaPendingCredential);
1365
+ const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo == phoneNumber);
1366
+ errors_1.assert(enrollment && enrollment.mfaEnrollmentId, "MFA_ENROLLMENT_NOT_FOUND");
1367
+ user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
1368
+ errors_1.assert(!user.disabled, "USER_DISABLED");
1369
+ const { idToken, refreshToken } = issueTokens(state, user, signInProvider, {
1370
+ secondFactor: { identifier: enrollment.mfaEnrollmentId, provider: state_1.PROVIDER_PHONE },
1371
+ });
1372
+ return {
1373
+ idToken,
1374
+ refreshToken,
1375
+ };
1376
+ }
1110
1377
  function coercePrimitiveToString(value) {
1111
1378
  switch (typeof value) {
1112
1379
  case "string":
@@ -1124,11 +1391,26 @@ function redactPasswordHash(user) {
1124
1391
  function hashPassword(password, salt) {
1125
1392
  return `fakeHash:salt=${salt}:password=${password}`;
1126
1393
  }
1127
- function issueTokens(state, user, signInProvider, extraClaims = {}) {
1128
- state.updateUserByLocalId(user.localId, { lastRefreshAt: new Date().toISOString() });
1394
+ function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, } = {}) {
1395
+ user = state.updateUserByLocalId(user.localId, { lastRefreshAt: new Date().toISOString() });
1396
+ const usageMode = state.usageMode === state_1.UsageMode.PASSTHROUGH ? "passthrough" : undefined;
1397
+ const tenantId = state instanceof state_1.TenantProjectState ? state.tenantId : undefined;
1129
1398
  const expiresInSeconds = 60 * 60;
1130
- const idToken = generateJwt(state.projectId, user, signInProvider, expiresInSeconds, extraClaims);
1131
- const refreshToken = state.createRefreshTokenFor(user, signInProvider, extraClaims);
1399
+ const idToken = generateJwt(user, {
1400
+ projectId: state.projectId,
1401
+ signInProvider,
1402
+ expiresInSeconds,
1403
+ extraClaims,
1404
+ secondFactor,
1405
+ usageMode,
1406
+ tenantId,
1407
+ });
1408
+ const refreshToken = state.usageMode === state_1.UsageMode.DEFAULT
1409
+ ? state.createRefreshTokenFor(user, signInProvider, {
1410
+ extraClaims,
1411
+ secondFactor,
1412
+ })
1413
+ : undefined;
1132
1414
  return {
1133
1415
  idToken,
1134
1416
  refreshToken,
@@ -1136,11 +1418,16 @@ function issueTokens(state, user, signInProvider, extraClaims = {}) {
1136
1418
  };
1137
1419
  }
1138
1420
  function parseIdToken(state, idToken) {
1421
+ errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
1139
1422
  const decoded = jsonwebtoken_1.decode(idToken, { complete: true });
1140
1423
  errors_1.assert(decoded, "INVALID_ID_TOKEN");
1141
1424
  if (decoded.header.alg !== "none") {
1142
1425
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("WARN", "Received a signed JWT. Auth Emulator does not validate JWTs and IS NOT SECURE");
1143
1426
  }
1427
+ if (decoded.payload.firebase.tenant) {
1428
+ errors_1.assert(state instanceof state_1.TenantProjectState, "((Parsed token that belongs to tenant in a non-tenant project.))");
1429
+ errors_1.assert(decoded.payload.firebase.tenant === state.tenantId, "TENANT_ID_MISMATCH");
1430
+ }
1144
1431
  const user = state.getUserByLocalId(decoded.payload.user_id);
1145
1432
  errors_1.assert(user, "USER_NOT_FOUND");
1146
1433
  errors_1.assert(!user.validSince || decoded.payload.iat >= Number(user.validSince), "TOKEN_EXPIRED");
@@ -1148,7 +1435,7 @@ function parseIdToken(state, idToken) {
1148
1435
  const signInProvider = decoded.payload.firebase.sign_in_provider;
1149
1436
  return { user, signInProvider, payload: decoded.payload };
1150
1437
  }
1151
- function generateJwt(projectId, user, signInProvider, expiresInSeconds, extraClaims = {}) {
1438
+ function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, usageMode, tenantId, }) {
1152
1439
  const identities = {};
1153
1440
  if (user.email) {
1154
1441
  identities["email"] = [user.email];
@@ -1168,6 +1455,10 @@ function generateJwt(projectId, user, signInProvider, expiresInSeconds, extraCla
1168
1455
  const customPayloadFields = Object.assign(Object.assign(Object.assign({ name: user.displayName, picture: user.photoUrl }, customAttributes), extraClaims), { email: user.email, email_verified: user.emailVerified, phone_number: user.phoneNumber, provider_id: signInProvider === "anonymous" ? signInProvider : undefined, auth_time: utils_1.toUnixTimestamp(getAuthTime(user)), user_id: user.localId, firebase: {
1169
1456
  identities,
1170
1457
  sign_in_provider: signInProvider,
1458
+ second_factor_identifier: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.identifier,
1459
+ sign_in_second_factor: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.provider,
1460
+ usage_mode: usageMode,
1461
+ tenant: tenantId,
1171
1462
  } });
1172
1463
  const jwtStr = jsonwebtoken_1.sign(customPayloadFields, "", {
1173
1464
  algorithm: "none",
@@ -1261,7 +1552,7 @@ function getMfaEnrollmentsFromRequest(state, request, options) {
1261
1552
  : enrollment.mfaEnrollmentId;
1262
1553
  errors_1.assert(mfaEnrollmentId, "INVALID_MFA_ENROLLMENT_ID : mfaEnrollmentId must be defined.");
1263
1554
  errors_1.assert(!enrollmentIds.has(mfaEnrollmentId), "DUPLICATE_MFA_ENROLLMENT_ID");
1264
- enrollments.push(Object.assign(Object.assign({}, enrollment), { mfaEnrollmentId }));
1555
+ enrollments.push(Object.assign(Object.assign({}, enrollment), { mfaEnrollmentId, unobfuscatedPhoneInfo: enrollment.phoneInfo }));
1265
1556
  phoneNumbers.add(enrollment.phoneInfo);
1266
1557
  enrollmentIds.add(mfaEnrollmentId);
1267
1558
  }
@@ -1450,3 +1741,111 @@ function handleIdpSignUp(response, options) {
1450
1741
  accountUpdates,
1451
1742
  };
1452
1743
  }
1744
+ function mfaPending(state, user, signInProvider) {
1745
+ if (!user.mfaInfo) {
1746
+ throw new Error("Internal assertion error: mfaPending called on user without MFA.");
1747
+ }
1748
+ const pendingCredentialPayload = {
1749
+ _AuthEmulatorMfaPendingCredential: "DO NOT MODIFY",
1750
+ localId: user.localId,
1751
+ signInProvider,
1752
+ projectId: state.projectId,
1753
+ };
1754
+ if (state instanceof state_1.TenantProjectState) {
1755
+ pendingCredentialPayload.tenantId = state.tenantId;
1756
+ }
1757
+ const mfaPendingCredential = Buffer.from(JSON.stringify(pendingCredentialPayload), "utf8").toString("base64");
1758
+ return { mfaPendingCredential, mfaInfo: user.mfaInfo.map(redactMfaInfo) };
1759
+ }
1760
+ function redactMfaInfo(mfaInfo) {
1761
+ return {
1762
+ displayName: mfaInfo.displayName,
1763
+ enrolledAt: mfaInfo.enrolledAt,
1764
+ mfaEnrollmentId: mfaInfo.mfaEnrollmentId,
1765
+ phoneInfo: mfaInfo.unobfuscatedPhoneInfo
1766
+ ? obfuscatePhoneNumber(mfaInfo.unobfuscatedPhoneInfo)
1767
+ : undefined,
1768
+ };
1769
+ }
1770
+ function obfuscatePhoneNumber(phoneNumber) {
1771
+ const split = phoneNumber.split("");
1772
+ let digitsEncountered = 0;
1773
+ for (let i = split.length - 1; i >= 0; i--) {
1774
+ if (/[0-9]/.test(split[i])) {
1775
+ digitsEncountered++;
1776
+ if (digitsEncountered > 4) {
1777
+ split[i] = "*";
1778
+ }
1779
+ }
1780
+ }
1781
+ return split.join("");
1782
+ }
1783
+ function parsePendingCredential(state, pendingCredential) {
1784
+ let pendingCredentialPayload;
1785
+ try {
1786
+ const json = Buffer.from(pendingCredential, "base64").toString("utf8");
1787
+ pendingCredentialPayload = JSON.parse(json);
1788
+ }
1789
+ catch (_a) {
1790
+ errors_1.assert(false, "((Invalid phoneVerificationInfo.mfaPendingCredential.))");
1791
+ }
1792
+ errors_1.assert(pendingCredentialPayload._AuthEmulatorMfaPendingCredential, "((Invalid phoneVerificationInfo.mfaPendingCredential.))");
1793
+ errors_1.assert(pendingCredentialPayload.projectId === state.projectId, "INVALID_PROJECT_ID : Project ID does not match MFA pending credential.");
1794
+ if (state instanceof state_1.TenantProjectState) {
1795
+ errors_1.assert(pendingCredentialPayload.tenantId === state.tenantId, "INVALID_PROJECT_ID : Project ID does not match MFA pending credential.");
1796
+ }
1797
+ const { localId, signInProvider } = pendingCredentialPayload;
1798
+ const user = state.getUserByLocalId(localId);
1799
+ errors_1.assert(user, "((User in pendingCredentialPayload does not exist.))");
1800
+ return { user, signInProvider };
1801
+ }
1802
+ function createTenant(state, reqBody) {
1803
+ var _a, _b, _c, _d, _e;
1804
+ if (!(state instanceof state_1.AgentProjectState)) {
1805
+ throw new errors_1.InternalError("INTERNAL_ERROR: Can only create tenant in agent project", "INTERNAL");
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
+ }
1814
+ const tenant = {
1815
+ displayName: reqBody.displayName,
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,
1821
+ tenantId: "",
1822
+ };
1823
+ return state.createTenant(tenant);
1824
+ }
1825
+ function listTenants(state, reqBody, ctx) {
1826
+ errors_1.assert(state instanceof state_1.AgentProjectState, "((Can only list tenants in agent project.))");
1827
+ const pageSize = Math.min(Math.floor(ctx.params.query.pageSize) || 20, 1000);
1828
+ const tenants = state.listTenants(ctx.params.query.pageToken);
1829
+ let nextPageToken = undefined;
1830
+ if (pageSize > 0 && tenants.length >= pageSize) {
1831
+ tenants.length = pageSize;
1832
+ nextPageToken = tenants[tenants.length - 1].tenantId;
1833
+ }
1834
+ return {
1835
+ nextPageToken,
1836
+ tenants,
1837
+ };
1838
+ }
1839
+ function deleteTenant(state, reqBody, ctx) {
1840
+ errors_1.assert(state instanceof state_1.TenantProjectState, "((Can only delete tenant on tenant projects.))");
1841
+ state.delete();
1842
+ return {};
1843
+ }
1844
+ function getTenant(state, reqBody, ctx) {
1845
+ errors_1.assert(state instanceof state_1.TenantProjectState, "((Can only get tenant on tenant projects.))");
1846
+ return state.tenantConfig;
1847
+ }
1848
+ function updateTenant(state, reqBody, ctx) {
1849
+ errors_1.assert(state instanceof state_1.TenantProjectState, "((Can only update tenant on tenant projects.))");
1850
+ return state.updateTenant(reqBody, ctx.params.query.updateMask);
1851
+ }