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.
- package/CHANGELOG.md +1 -3
- package/lib/api.js +1 -0
- package/lib/apiv2.js +1 -1
- package/lib/appdistribution/client.js +84 -72
- package/lib/appdistribution/distribution.js +8 -26
- package/lib/appdistribution/options-parser-util.js +51 -0
- package/lib/command.js +8 -6
- package/lib/commands/appdistribution-distribute.js +74 -91
- package/lib/commands/appdistribution-testers-add.js +18 -0
- package/lib/commands/appdistribution-testers-remove.js +32 -0
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/ext-configure.js +9 -1
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-init.js +18 -9
- package/lib/commands/ext-dev-publish.js +11 -4
- package/lib/commands/ext-dev-unpublish.js +2 -1
- package/lib/commands/ext-install.js +115 -48
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +67 -43
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +44 -35
- package/lib/commands/functions-list.js +54 -0
- package/lib/commands/functions-log.js +5 -22
- package/lib/commands/hosting-channel-deploy.js +6 -4
- package/lib/commands/index.js +12 -0
- package/lib/deploy/functions/backend.js +47 -12
- package/lib/deploy/functions/containerCleaner.js +5 -1
- package/lib/deploy/functions/deploy.js +7 -5
- package/lib/deploy/functions/prepare.js +9 -7
- package/lib/deploy/functions/prompts.js +3 -21
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
- package/lib/deploy/functions/runtimes/index.js +2 -1
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +4 -3
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +14 -9
- package/lib/deploy/functions/triggerRegionHelper.js +32 -0
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1758 -404
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +429 -40
- package/lib/emulator/auth/server.js +18 -11
- package/lib/emulator/auth/state.js +186 -5
- package/lib/emulator/auth/widget_ui.js +2 -2
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/emulatorLogger.js +0 -3
- package/lib/emulator/events/types.js +16 -0
- package/lib/emulator/functionsEmulator.js +102 -17
- package/lib/emulator/functionsEmulatorRuntime.js +46 -121
- package/lib/emulator/functionsEmulatorShared.js +51 -7
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/pubsubEmulator.js +61 -40
- package/lib/extensions/askUserForConsent.js +16 -13
- package/lib/extensions/askUserForParam.js +72 -3
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +93 -0
- package/lib/extensions/displayExtensionInfo.js +38 -38
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/extensionsApi.js +69 -95
- package/lib/extensions/extensionsHelper.js +75 -50
- package/lib/extensions/paramHelper.js +79 -36
- package/lib/extensions/refs.js +59 -0
- package/lib/extensions/resolveSource.js +2 -20
- package/lib/extensions/secretsUtils.js +58 -0
- package/lib/extensions/updateHelper.js +39 -105
- package/lib/extensions/warnings.js +1 -7
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/functionslog.js +40 -0
- package/lib/functions/listFunctions.js +10 -0
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/cloudfunctions.js +84 -9
- package/lib/gcp/cloudfunctionsv2.js +99 -7
- package/lib/gcp/cloudlogging.js +27 -21
- package/lib/gcp/secretManager.js +111 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/package.json +5 -4
- package/schema/firebase-config.json +2 -1
- package/templates/extensions/CHANGELOG.md +7 -0
- 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
|
-
|
|
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
|
|
936
|
+
let extraClaims = {};
|
|
841
937
|
if ("claims" in payload) {
|
|
842
938
|
validateCustomClaims(payload.claims);
|
|
843
|
-
|
|
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
|
-
|
|
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
|
-
|
|
897
|
-
|
|
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(
|
|
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
|
-
|
|
993
|
-
|
|
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
|
-
|
|
1011
|
-
|
|
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,
|
|
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(
|
|
1131
|
-
|
|
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(
|
|
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
|
+
}
|