firebase-tools 9.19.0 → 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 (39) hide show
  1. package/CHANGELOG.md +1 -3
  2. package/lib/api.js +1 -0
  3. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  4. package/lib/commands/ext-configure.js +2 -0
  5. package/lib/commands/ext-install.js +23 -2
  6. package/lib/commands/ext-uninstall.js +6 -0
  7. package/lib/commands/ext-update.js +9 -2
  8. package/lib/commands/functions-config-export.js +115 -0
  9. package/lib/commands/functions-delete.js +44 -35
  10. package/lib/commands/functions-list.js +1 -1
  11. package/lib/commands/index.js +8 -0
  12. package/lib/deploy/functions/backend.js +13 -4
  13. package/lib/deploy/functions/prepare.js +3 -0
  14. package/lib/deploy/functions/runtimes/node/parseTriggers.js +13 -1
  15. package/lib/deploy/functions/triggerRegionHelper.js +32 -0
  16. package/lib/downloadUtils.js +37 -0
  17. package/lib/emulator/auth/apiSpec.js +513 -1
  18. package/lib/emulator/auth/handlers.js +4 -3
  19. package/lib/emulator/auth/operations.js +144 -14
  20. package/lib/emulator/auth/server.js +10 -13
  21. package/lib/emulator/auth/state.js +132 -13
  22. package/lib/emulator/download.js +2 -31
  23. package/lib/emulator/functionsEmulatorRuntime.js +29 -7
  24. package/lib/extensions/askUserForConsent.js +14 -1
  25. package/lib/extensions/askUserForParam.js +72 -3
  26. package/lib/extensions/extensionsApi.js +1 -0
  27. package/lib/extensions/extensionsHelper.js +1 -0
  28. package/lib/extensions/paramHelper.js +3 -2
  29. package/lib/extensions/secretsUtils.js +58 -0
  30. package/lib/functional.js +8 -1
  31. package/lib/functions/env.js +10 -4
  32. package/lib/functions/runtimeConfigExport.js +137 -0
  33. package/lib/gcp/cloudfunctions.js +4 -2
  34. package/lib/gcp/cloudfunctionsv2.js +6 -0
  35. package/lib/gcp/secretManager.js +111 -0
  36. package/lib/gcp/storage.js +16 -0
  37. package/lib/previews.js +1 -1
  38. package/lib/requireInteractive.js +12 -0
  39. package/package.json +1 -1
@@ -7,7 +7,7 @@ const errors_1 = require("./errors");
7
7
  const widget_ui_1 = require("./widget_ui");
8
8
  function registerHandlers(app, getProjectStateByApiKey) {
9
9
  app.get(`/emulator/action`, (req, res) => {
10
- const { mode, oobCode, continueUrl, apiKey } = req.query;
10
+ const { mode, oobCode, continueUrl, apiKey, tenantId } = req.query;
11
11
  if (!apiKey) {
12
12
  return res.status(400).json({
13
13
  authEmulator: {
@@ -24,7 +24,7 @@ function registerHandlers(app, getProjectStateByApiKey) {
24
24
  },
25
25
  });
26
26
  }
27
- const state = getProjectStateByApiKey(apiKey);
27
+ const state = getProjectStateByApiKey(apiKey, tenantId);
28
28
  switch (mode) {
29
29
  case "recoverEmail": {
30
30
  const oob = state.validateOobCode(oobCode);
@@ -159,6 +159,7 @@ function registerHandlers(app, getProjectStateByApiKey) {
159
159
  res.set("Content-Type", "text/html; charset=utf-8");
160
160
  const apiKey = req.query.apiKey;
161
161
  const providerId = req.query.providerId;
162
+ const tenantId = req.query.tenantId;
162
163
  if (!apiKey || !providerId) {
163
164
  return res.status(400).json({
164
165
  authEmulator: {
@@ -166,7 +167,7 @@ function registerHandlers(app, getProjectStateByApiKey) {
166
167
  },
167
168
  });
168
169
  }
169
- const state = getProjectStateByApiKey(apiKey);
170
+ const state = getProjectStateByApiKey(apiKey, tenantId);
170
171
  const providerInfos = state.listProviderInfosByProviderId(providerId);
171
172
  const options = providerInfos
172
173
  .map((info) => `<li class="js-reuse-account mdc-list-item mdc-ripple-upgraded" tabindex="0" data-id-token="${encodeURIComponent(createFakeClaims(info))}">
@@ -50,6 +50,25 @@ exports.authOperations = {
50
50
  batchDelete,
51
51
  batchGet,
52
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
+ },
53
72
  },
54
73
  },
55
74
  securetoken: {
@@ -83,6 +102,7 @@ const MFA_INELIGIBLE_PROVIDER = new Set([
83
102
  ]);
84
103
  function signUp(state, reqBody, ctx) {
85
104
  var _a;
105
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
86
106
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
87
107
  let provider;
88
108
  const updates = {
@@ -115,9 +135,11 @@ function signUp(state, reqBody, ctx) {
115
135
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
116
136
  errors_1.assert(reqBody.password, "MISSING_PASSWORD");
117
137
  provider = state_1.PROVIDER_PASSWORD;
138
+ errors_1.assert(state.allowPasswordSignup, "OPERATION_NOT_ALLOWED");
118
139
  }
119
140
  else {
120
141
  provider = state_1.PROVIDER_ANONYMOUS;
142
+ errors_1.assert(state.enableAnonymousUser, "ADMIN_ONLY_OPERATION");
121
143
  }
122
144
  }
123
145
  if (reqBody.email) {
@@ -138,6 +160,9 @@ function signUp(state, reqBody, ctx) {
138
160
  generateEnrollmentIds: true,
139
161
  });
140
162
  }
163
+ if (reqBody.tenantId) {
164
+ updates.tenantId = reqBody.tenantId;
165
+ }
141
166
  let user;
142
167
  if (reqBody.idToken) {
143
168
  ({ user } = parseIdToken(state, reqBody.idToken));
@@ -158,6 +183,7 @@ function signUp(state, reqBody, ctx) {
158
183
  }
159
184
  function lookup(state, reqBody, ctx) {
160
185
  var _a, _b, _c, _d, _e;
186
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
161
187
  const seenLocalIds = new Set();
162
188
  const users = [];
163
189
  function tryAddUser(maybeUser) {
@@ -198,6 +224,7 @@ function lookup(state, reqBody, ctx) {
198
224
  }
199
225
  function batchCreate(state, reqBody) {
200
226
  var _a, _b;
227
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
201
228
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
202
229
  errors_1.assert((_a = reqBody.users) === null || _a === void 0 ? void 0 : _a.length, "MISSING_USER_ACCOUNT");
203
230
  if (reqBody.sanityCheck) {
@@ -238,6 +265,10 @@ function batchCreate(state, reqBody) {
238
265
  photoUrl: userInfo.photoUrl,
239
266
  lastLoginAt: userInfo.lastLoginAt,
240
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
+ }
241
272
  if (userInfo.passwordHash) {
242
273
  fields.passwordHash = userInfo.passwordHash;
243
274
  fields.salt = userInfo.salt;
@@ -369,6 +400,7 @@ function batchDelete(state, reqBody) {
369
400
  return { errors: errors.length ? errors : undefined };
370
401
  }
371
402
  function batchGet(state, reqBody, ctx) {
403
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
372
404
  const maxResults = Math.min(Math.floor(ctx.params.query.maxResults) || 20, 1000);
373
405
  const users = state.queryUsers({}, { sortByField: "localId", order: "ASC", startToken: ctx.params.query.nextPageToken });
374
406
  let newPageToken = undefined;
@@ -386,6 +418,7 @@ function batchGet(state, reqBody, ctx) {
386
418
  }
387
419
  function createAuthUri(state, reqBody) {
388
420
  var _a;
421
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
389
422
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
390
423
  const sessionId = reqBody.sessionId || utils_1.randomId(27);
391
424
  if (reqBody.providerId) {
@@ -461,6 +494,7 @@ function createSessionCookie(state, reqBody, ctx) {
461
494
  }
462
495
  function deleteAccount(state, reqBody, ctx) {
463
496
  var _a;
497
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
464
498
  let user;
465
499
  if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
466
500
  errors_1.assert(reqBody.localId, "MISSING_LOCAL_ID");
@@ -478,6 +512,8 @@ function deleteAccount(state, reqBody, ctx) {
478
512
  };
479
513
  }
480
514
  function getProjects(state) {
515
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
516
+ errors_1.assert(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
481
517
  return {
482
518
  projectId: state.projectNumber,
483
519
  authorizedDomains: [
@@ -485,7 +521,8 @@ function getProjects(state) {
485
521
  ],
486
522
  };
487
523
  }
488
- function getRecaptchaParams() {
524
+ function getRecaptchaParams(state) {
525
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
489
526
  return {
490
527
  kind: "identitytoolkit#GetRecaptchaParamResponse",
491
528
  recaptchaStoken: "This-is-a-fake-token__Dont-send-this-to-the-Recaptcha-service__The-Auth-Emulator-does-not-support-Recaptcha",
@@ -494,6 +531,7 @@ function getRecaptchaParams() {
494
531
  }
495
532
  function queryAccounts(state, reqBody) {
496
533
  var _a;
534
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
497
535
  if ((_a = reqBody.expression) === null || _a === void 0 ? void 0 : _a.length) {
498
536
  throw new errors_1.NotImplementedError("expression is not implemented.");
499
537
  }
@@ -530,7 +568,9 @@ function queryAccounts(state, reqBody) {
530
568
  }
531
569
  function resetPassword(state, reqBody) {
532
570
  var _a;
571
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
533
572
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
573
+ errors_1.assert(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
534
574
  errors_1.assert(reqBody.oobCode, "MISSING_OOB_CODE");
535
575
  const oob = state.validateOobCode(reqBody.oobCode);
536
576
  errors_1.assert(oob, "INVALID_OOB_CODE");
@@ -559,6 +599,7 @@ function resetPassword(state, reqBody) {
559
599
  exports.resetPassword = resetPassword;
560
600
  function sendOobCode(state, reqBody, ctx) {
561
601
  var _a;
602
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
562
603
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
563
604
  errors_1.assert(reqBody.requestType && reqBody.requestType !== "OOB_REQ_TYPE_UNSPECIFIED", "MISSING_REQ_TYPE");
564
605
  if (reqBody.returnOobLink) {
@@ -571,6 +612,7 @@ function sendOobCode(state, reqBody, ctx) {
571
612
  let mode;
572
613
  switch (reqBody.requestType) {
573
614
  case "EMAIL_SIGNIN":
615
+ errors_1.assert(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
574
616
  mode = "signIn";
575
617
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
576
618
  email = utils_1.canonicalizeEmailAddress(reqBody.email);
@@ -625,6 +667,8 @@ function sendOobCode(state, reqBody, ctx) {
625
667
  }
626
668
  function sendVerificationCode(state, reqBody) {
627
669
  var _a;
670
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
671
+ errors_1.assert(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
628
672
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
629
673
  errors_1.assert(reqBody.phoneNumber && utils_1.isValidPhoneNumber(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
630
674
  const user = state.getUserByPhoneNumber(reqBody.phoneNumber);
@@ -637,6 +681,7 @@ function sendVerificationCode(state, reqBody) {
637
681
  }
638
682
  function setAccountInfo(state, reqBody, ctx) {
639
683
  var _a;
684
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
640
685
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
641
686
  const url = utils_1.authEmulatorUrl(ctx.req);
642
687
  return setAccountInfoImpl(state, reqBody, {
@@ -831,6 +876,9 @@ function createOobRecord(state, email, url, params) {
831
876
  if (params.continueUrl) {
832
877
  url.searchParams.set("continueUrl", params.continueUrl);
833
878
  }
879
+ if (state instanceof state_1.TenantProjectState) {
880
+ url.searchParams.set("tenantId", state.tenantId);
881
+ }
834
882
  return url.toString();
835
883
  });
836
884
  return oobRecord;
@@ -859,6 +907,7 @@ function logOobMessage(oobRecord) {
859
907
  }
860
908
  function signInWithCustomToken(state, reqBody) {
861
909
  var _a;
910
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
862
911
  errors_1.assert(reqBody.token, "MISSING_CUSTOM_TOKEN");
863
912
  let payload;
864
913
  if (reqBody.token.startsWith("{")) {
@@ -871,6 +920,9 @@ function signInWithCustomToken(state, reqBody) {
871
920
  }
872
921
  else {
873
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
+ }
874
926
  errors_1.assert(decoded, "INVALID_CUSTOM_TOKEN : Invalid assertion format");
875
927
  if (decoded.header.alg !== "none") {
876
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");
@@ -891,6 +943,7 @@ function signInWithCustomToken(state, reqBody) {
891
943
  const updates = {
892
944
  customAuth: true,
893
945
  lastLoginAt: Date.now().toString(),
946
+ tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined,
894
947
  };
895
948
  if (user) {
896
949
  errors_1.assert(!user.disabled, "USER_DISABLED");
@@ -906,6 +959,8 @@ function signInWithCustomToken(state, reqBody) {
906
959
  }
907
960
  function signInWithEmailLink(state, reqBody) {
908
961
  var _a;
962
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
963
+ errors_1.assert(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
909
964
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
910
965
  const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
911
966
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
@@ -920,6 +975,9 @@ function signInWithEmailLink(state, reqBody) {
920
975
  emailVerified: true,
921
976
  emailLinkSignin: true,
922
977
  };
978
+ if (state instanceof state_1.TenantProjectState) {
979
+ updates.tenantId = state.tenantId;
980
+ }
923
981
  let user = state.getUserByEmail(email);
924
982
  const isNewUser = !user && !userFromIdToken;
925
983
  if (!user) {
@@ -941,7 +999,7 @@ function signInWithEmailLink(state, reqBody) {
941
999
  localId: user.localId,
942
1000
  isNewUser,
943
1001
  };
944
- if ((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length) {
1002
+ if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length)) {
945
1003
  return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
946
1004
  }
947
1005
  else {
@@ -951,6 +1009,7 @@ function signInWithEmailLink(state, reqBody) {
951
1009
  }
952
1010
  function signInWithIdp(state, reqBody) {
953
1011
  var _a, _b;
1012
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
954
1013
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
955
1014
  if (reqBody.returnRefreshToken) {
956
1015
  throw new errors_1.NotImplementedError("returnRefreshToken is not implemented yet.");
@@ -1023,7 +1082,7 @@ function signInWithIdp(state, reqBody) {
1023
1082
  };
1024
1083
  let user;
1025
1084
  if (response.isNewUser) {
1026
- 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 }));
1027
1086
  response.localId = user.localId;
1028
1087
  }
1029
1088
  else {
@@ -1037,7 +1096,10 @@ function signInWithIdp(state, reqBody) {
1037
1096
  if (user.email === response.email) {
1038
1097
  response.emailVerified = user.emailVerified;
1039
1098
  }
1040
- if ((_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.length) {
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)) {
1041
1103
  return Object.assign(Object.assign({}, response), mfaPending(state, user, providerId));
1042
1104
  }
1043
1105
  else {
@@ -1047,6 +1109,8 @@ function signInWithIdp(state, reqBody) {
1047
1109
  }
1048
1110
  function signInWithPassword(state, reqBody) {
1049
1111
  var _a;
1112
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1113
+ errors_1.assert(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
1050
1114
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
1051
1115
  errors_1.assert(reqBody.email, "MISSING_EMAIL");
1052
1116
  errors_1.assert(reqBody.password, "MISSING_PASSWORD");
@@ -1068,7 +1132,7 @@ function signInWithPassword(state, reqBody) {
1068
1132
  localId: user.localId,
1069
1133
  email,
1070
1134
  };
1071
- if ((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length) {
1135
+ if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length)) {
1072
1136
  return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
1073
1137
  }
1074
1138
  else {
@@ -1078,6 +1142,8 @@ function signInWithPassword(state, reqBody) {
1078
1142
  }
1079
1143
  function signInWithPhoneNumber(state, reqBody) {
1080
1144
  var _a;
1145
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1146
+ errors_1.assert(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
1081
1147
  errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
1082
1148
  let phoneNumber;
1083
1149
  if (reqBody.temporaryProof) {
@@ -1191,7 +1257,9 @@ function listVerificationCodesInProject(state) {
1191
1257
  };
1192
1258
  }
1193
1259
  function mfaEnrollmentStart(state, reqBody) {
1194
- var _a;
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.");
1195
1263
  errors_1.assert(reqBody.idToken, "MISSING_ID_TOKEN");
1196
1264
  const { user, signInProvider } = parseIdToken(state, reqBody.idToken);
1197
1265
  errors_1.assert(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
@@ -1199,7 +1267,7 @@ function mfaEnrollmentStart(state, reqBody) {
1199
1267
  errors_1.assert(reqBody.phoneEnrollmentInfo, "INVALID_ARGUMENT : ((Missing phoneEnrollmentInfo.))");
1200
1268
  const phoneNumber = reqBody.phoneEnrollmentInfo.phoneNumber;
1201
1269
  errors_1.assert(phoneNumber && utils_1.isValidPhoneNumber(phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
1202
- errors_1.assert(!((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.some((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber)), "SECOND_FACTOR_EXISTS : Phone number already enrolled as second factor for this account.");
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.");
1203
1271
  const { sessionInfo, code } = state.createVerificationCode(phoneNumber);
1204
1272
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To enroll MFA with ${phoneNumber}, use the code ${code}.`);
1205
1273
  return {
@@ -1209,7 +1277,9 @@ function mfaEnrollmentStart(state, reqBody) {
1209
1277
  };
1210
1278
  }
1211
1279
  function mfaEnrollmentFinalize(state, reqBody) {
1212
- var _a;
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.");
1213
1283
  errors_1.assert(reqBody.idToken, "MISSING_ID_TOKEN");
1214
1284
  let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
1215
1285
  errors_1.assert(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
@@ -1221,7 +1291,7 @@ function mfaEnrollmentFinalize(state, reqBody) {
1221
1291
  errors_1.assert(code, "MISSING_CODE");
1222
1292
  errors_1.assert(sessionInfo, "MISSING_SESSION_INFO");
1223
1293
  const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
1224
- errors_1.assert(!((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.some((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber)), "SECOND_FACTOR_EXISTS : Phone number already enrolled as second factor for this account.");
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.");
1225
1295
  const existingFactors = user.mfaInfo || [];
1226
1296
  const existingIds = new Set();
1227
1297
  for (const { mfaEnrollmentId } of existingFactors) {
@@ -1248,6 +1318,7 @@ function mfaEnrollmentFinalize(state, reqBody) {
1248
1318
  };
1249
1319
  }
1250
1320
  function mfaEnrollmentWithdraw(state, reqBody) {
1321
+ errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
1251
1322
  errors_1.assert(reqBody.idToken, "MISSING_ID_TOKEN");
1252
1323
  let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
1253
1324
  errors_1.assert(user.mfaInfo, "MFA_ENROLLMENT_NOT_FOUND");
@@ -1257,11 +1328,13 @@ function mfaEnrollmentWithdraw(state, reqBody) {
1257
1328
  return Object.assign({}, issueTokens(state, user, signInProvider));
1258
1329
  }
1259
1330
  function mfaSignInStart(state, reqBody) {
1260
- var _a;
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.");
1261
1334
  errors_1.assert(reqBody.mfaPendingCredential, "MISSING_MFA_PENDING_CREDENTIAL : Request does not have MFA pending credential.");
1262
1335
  errors_1.assert(reqBody.mfaEnrollmentId, "MISSING_MFA_ENROLLMENT_ID : No second factor identifier is provided.");
1263
1336
  const { user } = parsePendingCredential(state, reqBody.mfaPendingCredential);
1264
- const enrollment = (_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.find((factor) => factor.mfaEnrollmentId === reqBody.mfaEnrollmentId);
1337
+ const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((factor) => factor.mfaEnrollmentId === reqBody.mfaEnrollmentId);
1265
1338
  errors_1.assert(enrollment, "MFA_ENROLLMENT_NOT_FOUND");
1266
1339
  const phoneNumber = enrollment.unobfuscatedPhoneInfo;
1267
1340
  errors_1.assert(phoneNumber, "INVALID_ARGUMENT : MFA provider not supported!");
@@ -1274,7 +1347,9 @@ function mfaSignInStart(state, reqBody) {
1274
1347
  };
1275
1348
  }
1276
1349
  function mfaSignInFinalize(state, reqBody) {
1277
- var _a;
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.");
1278
1353
  errors_1.assert(reqBody.mfaPendingCredential, "MISSING_CREDENTIAL : Please set MFA Pending Credential.");
1279
1354
  errors_1.assert(reqBody.phoneVerificationInfo, "INVALID_ARGUMENT : MFA provider not supported!");
1280
1355
  if (reqBody.phoneVerificationInfo.androidVerificationProof) {
@@ -1285,7 +1360,7 @@ function mfaSignInFinalize(state, reqBody) {
1285
1360
  errors_1.assert(sessionInfo, "MISSING_SESSION_INFO");
1286
1361
  const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
1287
1362
  let { user, signInProvider } = parsePendingCredential(state, reqBody.mfaPendingCredential);
1288
- const enrollment = (_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.find((enrollment) => enrollment.unobfuscatedPhoneInfo == phoneNumber);
1363
+ const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo == phoneNumber);
1289
1364
  errors_1.assert(enrollment && enrollment.mfaEnrollmentId, "MFA_ENROLLMENT_NOT_FOUND");
1290
1365
  user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
1291
1366
  errors_1.assert(!user.disabled, "USER_DISABLED");
@@ -1317,6 +1392,7 @@ function hashPassword(password, salt) {
1317
1392
  function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, } = {}) {
1318
1393
  user = state.updateUserByLocalId(user.localId, { lastRefreshAt: new Date().toISOString() });
1319
1394
  const usageMode = state.usageMode === state_1.UsageMode.PASSTHROUGH ? "passthrough" : undefined;
1395
+ const tenantId = state instanceof state_1.TenantProjectState ? state.tenantId : undefined;
1320
1396
  const expiresInSeconds = 60 * 60;
1321
1397
  const idToken = generateJwt(user, {
1322
1398
  projectId: state.projectId,
@@ -1325,6 +1401,7 @@ function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, }
1325
1401
  extraClaims,
1326
1402
  secondFactor,
1327
1403
  usageMode,
1404
+ tenantId,
1328
1405
  });
1329
1406
  const refreshToken = state.usageMode === state_1.UsageMode.DEFAULT
1330
1407
  ? state.createRefreshTokenFor(user, signInProvider, {
@@ -1345,6 +1422,10 @@ function parseIdToken(state, idToken) {
1345
1422
  if (decoded.header.alg !== "none") {
1346
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");
1347
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
+ }
1348
1429
  const user = state.getUserByLocalId(decoded.payload.user_id);
1349
1430
  errors_1.assert(user, "USER_NOT_FOUND");
1350
1431
  errors_1.assert(!user.validSince || decoded.payload.iat >= Number(user.validSince), "TOKEN_EXPIRED");
@@ -1352,7 +1433,7 @@ function parseIdToken(state, idToken) {
1352
1433
  const signInProvider = decoded.payload.firebase.sign_in_provider;
1353
1434
  return { user, signInProvider, payload: decoded.payload };
1354
1435
  }
1355
- function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, usageMode, }) {
1436
+ function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, usageMode, tenantId, }) {
1356
1437
  const identities = {};
1357
1438
  if (user.email) {
1358
1439
  identities["email"] = [user.email];
@@ -1375,6 +1456,7 @@ function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraC
1375
1456
  second_factor_identifier: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.identifier,
1376
1457
  sign_in_second_factor: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.provider,
1377
1458
  usage_mode: usageMode,
1459
+ tenant: tenantId,
1378
1460
  } });
1379
1461
  const jwtStr = jsonwebtoken_1.sign(customPayloadFields, "", {
1380
1462
  algorithm: "none",
@@ -1667,6 +1749,9 @@ function mfaPending(state, user, signInProvider) {
1667
1749
  signInProvider,
1668
1750
  projectId: state.projectId,
1669
1751
  };
1752
+ if (state instanceof state_1.TenantProjectState) {
1753
+ pendingCredentialPayload.tenantId = state.tenantId;
1754
+ }
1670
1755
  const mfaPendingCredential = Buffer.from(JSON.stringify(pendingCredentialPayload), "utf8").toString("base64");
1671
1756
  return { mfaPendingCredential, mfaInfo: user.mfaInfo.map(redactMfaInfo) };
1672
1757
  }
@@ -1704,8 +1789,53 @@ function parsePendingCredential(state, pendingCredential) {
1704
1789
  }
1705
1790
  errors_1.assert(pendingCredentialPayload._AuthEmulatorMfaPendingCredential, "((Invalid phoneVerificationInfo.mfaPendingCredential.))");
1706
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
+ }
1707
1795
  const { localId, signInProvider } = pendingCredentialPayload;
1708
1796
  const user = state.getUserByLocalId(localId);
1709
1797
  errors_1.assert(user, "((User in pendingCredentialPayload does not exist.))");
1710
1798
  return { user, signInProvider };
1711
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
+ }
@@ -83,7 +83,7 @@ async function createApp(defaultProjectId, projectStateForId = new Map()) {
83
83
  res.json(specWithEmulatorServer(req.protocol, req.headers.host));
84
84
  });
85
85
  registerLegacyRoutes(app);
86
- handlers_1.registerHandlers(app, (apiKey) => getProjectStateById(getProjectIdByApiKey(apiKey)));
86
+ handlers_1.registerHandlers(app, (apiKey, tenantId) => getProjectStateById(getProjectIdByApiKey(apiKey), tenantId));
87
87
  const apiKeyAuthenticator = (ctx, info) => {
88
88
  if (info.in !== "query") {
89
89
  throw new Error('apiKey must be defined as in: "query" in API spec.');
@@ -169,6 +169,9 @@ async function createApp(defaultProjectId, projectStateForId = new Map()) {
169
169
  "google-fieldmask"() {
170
170
  return true;
171
171
  },
172
+ "google-duration"() {
173
+ return true;
174
+ },
172
175
  uint64() {
173
176
  return true;
174
177
  },
@@ -235,21 +238,15 @@ async function createApp(defaultProjectId, projectStateForId = new Map()) {
235
238
  return defaultProjectId;
236
239
  }
237
240
  function getProjectStateById(projectId, tenantId) {
238
- let agentProject = projectStateForId.get(projectId);
239
- if (!agentProject) {
240
- const state = new state_1.AgentProjectState(projectId);
241
- agentProject = { state, tenantProjects: new Map() };
242
- projectStateForId.set(projectId, agentProject);
241
+ let agentState = projectStateForId.get(projectId);
242
+ if (!agentState) {
243
+ agentState = new state_1.AgentProjectState(projectId);
244
+ projectStateForId.set(projectId, agentState);
243
245
  }
244
246
  if (!tenantId) {
245
- return agentProject.state;
246
- }
247
- let tenantState = agentProject.tenantProjects.get(tenantId);
248
- if (!tenantState) {
249
- tenantState = new state_1.TenantProjectState(projectId, tenantId, agentProject.state);
250
- agentProject.tenantProjects.set(tenantId, tenantState);
247
+ return agentState;
251
248
  }
252
- return tenantState;
249
+ return agentState.getTenantProject(tenantId);
253
250
  }
254
251
  }
255
252
  exports.createApp = createApp;