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