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