firebase-tools
Version:
Command-Line Interface for Firebase
2,217 lines • 104 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseBlockingFunctionJwt = exports.setAccountInfoImpl = exports.resetPassword = exports.SESSION_COOKIE_MAX_VALID_DURATION = exports.CUSTOM_TOKEN_AUDIENCE = exports.authOperations = void 0;
const url_1 = require("url");
const jsonwebtoken_1 = require("jsonwebtoken");
const node_fetch_1 = require("node-fetch");
const abort_controller_1 = require("abort-controller");
const utils_1 = require("./utils");
const errors_1 = require("./errors");
const types_1 = require("../types");
const emulatorLogger_1 = require("../emulatorLogger");
const state_1 = require("./state");
exports.authOperations = {
identitytoolkit: {
getProjects,
getRecaptchaParams,
accounts: {
createAuthUri,
delete: deleteAccount,
lookup,
resetPassword,
sendOobCode,
sendVerificationCode,
signInWithCustomToken,
signInWithEmailLink,
signInWithIdp,
signInWithPassword,
signInWithPhoneNumber,
signUp,
update: setAccountInfo,
mfaEnrollment: {
finalize: mfaEnrollmentFinalize,
start: mfaEnrollmentStart,
withdraw: mfaEnrollmentWithdraw,
},
mfaSignIn: {
start: mfaSignInStart,
finalize: mfaSignInFinalize,
},
},
projects: {
createSessionCookie,
queryAccounts,
getConfig,
updateConfig,
accounts: {
_: signUp,
delete: deleteAccount,
lookup,
query: queryAccounts,
sendOobCode,
update: setAccountInfo,
batchCreate,
batchDelete,
batchGet,
},
tenants: {
create: createTenant,
delete: deleteTenant,
get: getTenant,
list: listTenants,
patch: updateTenant,
createSessionCookie,
accounts: {
_: signUp,
batchCreate,
batchDelete,
batchGet,
delete: deleteAccount,
lookup,
query: queryAccounts,
sendOobCode,
update: setAccountInfo,
},
},
},
},
securetoken: {
token: grantToken,
},
emulator: {
projects: {
accounts: {
delete: deleteAllAccountsInProject,
},
config: {
get: getEmulatorProjectConfig,
update: updateEmulatorProjectConfig,
},
oobCodes: {
list: listOobCodesInProject,
},
verificationCodes: {
list: listVerificationCodesInProject,
},
},
},
};
const PASSWORD_MIN_LENGTH = 6;
exports.CUSTOM_TOKEN_AUDIENCE = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit";
const MFA_INELIGIBLE_PROVIDER = new Set([
state_1.PROVIDER_ANONYMOUS,
state_1.PROVIDER_PHONE,
state_1.PROVIDER_CUSTOM,
state_1.PROVIDER_GAME_CENTER,
]);
async function signUp(state, reqBody, ctx) {
var _a, _b, _c, _d;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
let provider;
const timestamp = new Date();
let updates = {
lastLoginAt: timestamp.getTime().toString(),
};
if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
if (reqBody.idToken) {
(0, errors_1.assert)(!reqBody.localId, "UNEXPECTED_PARAMETER : User ID");
}
if (reqBody.localId) {
(0, errors_1.assert)(!state.getUserByLocalId(reqBody.localId), "DUPLICATE_LOCAL_ID");
}
updates.displayName = reqBody.displayName;
updates.photoUrl = reqBody.photoUrl;
updates.emailVerified = reqBody.emailVerified || false;
if (reqBody.phoneNumber) {
(0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
(0, errors_1.assert)(!state.getUserByPhoneNumber(reqBody.phoneNumber), "PHONE_NUMBER_EXISTS");
updates.phoneNumber = reqBody.phoneNumber;
}
if (reqBody.disabled) {
updates.disabled = true;
}
}
else {
(0, errors_1.assert)(!reqBody.localId, "UNEXPECTED_PARAMETER : User ID");
if (reqBody.idToken || reqBody.password || reqBody.email) {
updates.displayName = reqBody.displayName;
updates.emailVerified = false;
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
(0, errors_1.assert)(reqBody.password, "MISSING_PASSWORD");
provider = state_1.PROVIDER_PASSWORD;
(0, errors_1.assert)(state.allowPasswordSignup, "OPERATION_NOT_ALLOWED");
}
else {
provider = state_1.PROVIDER_ANONYMOUS;
(0, errors_1.assert)(state.enableAnonymousUser, "ADMIN_ONLY_OPERATION");
}
}
if (reqBody.email || (reqBody.email === "" && provider)) {
(0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
(0, errors_1.assert)(!state.getUserByEmail(email), "EMAIL_EXISTS");
updates.email = email;
}
if (reqBody.password) {
(0, errors_1.assert)(reqBody.password.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters`);
updates.salt = "fakeSalt" + (0, utils_1.randomId)(20);
updates.passwordHash = hashPassword(reqBody.password, updates.salt);
updates.passwordUpdatedAt = Date.now();
updates.validSince = (0, utils_1.toUnixTimestamp)(new Date()).toString();
}
if (reqBody.mfaInfo) {
updates.mfaInfo = getMfaEnrollmentsFromRequest(state, reqBody.mfaInfo, {
generateEnrollmentIds: true,
});
}
if (state instanceof state_1.TenantProjectState) {
updates.tenantId = state.tenantId;
}
let user;
if (reqBody.idToken) {
({ user } = parseIdToken(state, reqBody.idToken));
}
let extraClaims;
if (!user) {
updates.createdAt = timestamp.getTime().toString();
const localId = (_b = reqBody.localId) !== null && _b !== void 0 ? _b : state.generateLocalId();
if (reqBody.email && !((_c = ctx.security) === null || _c === void 0 ? void 0 : _c.Oauth2)) {
const userBeforeCreate = Object.assign({ localId }, updates);
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, { signInMethod: "password" });
updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
}
user = state.createUserWithLocalId(localId, updates);
(0, errors_1.assert)(user, "DUPLICATE_LOCAL_ID");
if (reqBody.email && !((_d = ctx.security) === null || _d === void 0 ? void 0 : _d.Oauth2)) {
if (!user.disabled) {
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "password" });
updates = blockingResponse.updates;
extraClaims = blockingResponse.extraClaims;
user = state.updateUserByLocalId(user.localId, updates);
}
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
}
}
else {
user = state.updateUserByLocalId(user.localId, updates);
}
return Object.assign({ kind: "identitytoolkit#SignupNewUserResponse", localId: user.localId, displayName: user.displayName, email: user.email }, (provider ? issueTokens(state, user, provider, { extraClaims }) : {}));
}
function lookup(state, reqBody, ctx) {
var _a, _b, _c, _d, _e;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
const seenLocalIds = new Set();
const users = [];
function tryAddUser(maybeUser) {
if (maybeUser && !seenLocalIds.has(maybeUser.localId)) {
users.push(maybeUser);
seenLocalIds.add(maybeUser.localId);
}
}
if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
if (reqBody.initialEmail) {
throw new errors_1.NotImplementedError("Lookup by initialEmail is not implemented.");
}
for (const localId of (_b = reqBody.localId) !== null && _b !== void 0 ? _b : []) {
tryAddUser(state.getUserByLocalId(localId));
}
for (const email of (_c = reqBody.email) !== null && _c !== void 0 ? _c : []) {
const canonicalizedEmail = (0, utils_1.canonicalizeEmailAddress)(email);
tryAddUser(state.getUserByEmail(canonicalizedEmail));
}
for (const phoneNumber of (_d = reqBody.phoneNumber) !== null && _d !== void 0 ? _d : []) {
tryAddUser(state.getUserByPhoneNumber(phoneNumber));
}
for (const { providerId, rawId } of (_e = reqBody.federatedUserId) !== null && _e !== void 0 ? _e : []) {
if (!providerId || !rawId) {
continue;
}
tryAddUser(state.getUserByProviderRawId(providerId, rawId));
}
}
else {
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
const { user } = parseIdToken(state, reqBody.idToken);
users.push(redactPasswordHash(user));
}
return {
kind: "identitytoolkit#GetAccountInfoResponse",
users: users.length ? users : undefined,
};
}
function batchCreate(state, reqBody) {
var _a, _b;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)((_a = reqBody.users) === null || _a === void 0 ? void 0 : _a.length, "MISSING_USER_ACCOUNT");
if (reqBody.sanityCheck) {
if (state.oneAccountPerEmail) {
const existingEmails = new Set();
for (const userInfo of reqBody.users) {
if (userInfo.email) {
(0, errors_1.assert)(!existingEmails.has(userInfo.email), `DUPLICATE_EMAIL : ${userInfo.email}`);
existingEmails.add(userInfo.email);
}
}
}
const existingProviderAccounts = new Set();
for (const userInfo of reqBody.users) {
for (const { providerId, rawId } of (_b = userInfo.providerUserInfo) !== null && _b !== void 0 ? _b : []) {
const key = `${providerId}:${rawId}`;
(0, errors_1.assert)(!existingProviderAccounts.has(key), `DUPLICATE_RAW_ID : Provider id(${providerId}), Raw id(${rawId})`);
existingProviderAccounts.add(key);
}
}
}
if (!reqBody.allowOverwrite) {
const existingLocalIds = new Set();
for (const userInfo of reqBody.users) {
const localId = userInfo.localId || "";
(0, errors_1.assert)(!existingLocalIds.has(localId), `DUPLICATE_LOCAL_ID : ${localId}`);
existingLocalIds.add(localId);
}
}
const errors = [];
for (let index = 0; index < reqBody.users.length; index++) {
const userInfo = reqBody.users[index];
try {
(0, errors_1.assert)(userInfo.localId, "localId is missing");
const uploadTime = new Date();
const fields = {
displayName: userInfo.displayName,
photoUrl: userInfo.photoUrl,
lastLoginAt: userInfo.lastLoginAt,
};
if (userInfo.tenantId) {
(0, errors_1.assert)(state instanceof state_1.TenantProjectState && state.tenantId === userInfo.tenantId, "Tenant id in userInfo does not match the tenant id in request.");
}
if (state instanceof state_1.TenantProjectState) {
fields.tenantId = state.tenantId;
}
if (userInfo.passwordHash) {
fields.passwordHash = userInfo.passwordHash;
fields.salt = userInfo.salt;
fields.passwordUpdatedAt = uploadTime.getTime();
}
else if (userInfo.rawPassword) {
fields.salt = userInfo.salt || "fakeSalt" + (0, utils_1.randomId)(20);
fields.passwordHash = hashPassword(userInfo.rawPassword, fields.salt);
fields.passwordUpdatedAt = uploadTime.getTime();
}
if (userInfo.customAttributes) {
validateSerializedCustomClaims(userInfo.customAttributes);
fields.customAttributes = userInfo.customAttributes;
}
if (userInfo.providerUserInfo) {
fields.providerUserInfo = [];
for (const providerUserInfo of userInfo.providerUserInfo) {
const { providerId, rawId, federatedId } = providerUserInfo;
if (providerId === state_1.PROVIDER_PASSWORD || providerId === state_1.PROVIDER_PHONE) {
continue;
}
if (!rawId || !providerId) {
if (!federatedId) {
(0, errors_1.assert)(false, "federatedId or (providerId & rawId) is required");
}
else {
(0, errors_1.assert)(false, "((Parsing federatedId is not implemented in Auth Emulator; please specify providerId AND rawId as a workaround.))");
}
}
const existingUserWithRawId = state.getUserByProviderRawId(providerId, rawId);
(0, errors_1.assert)(!existingUserWithRawId || existingUserWithRawId.localId === userInfo.localId, "raw id exists in other account in database");
fields.providerUserInfo.push(Object.assign(Object.assign({}, providerUserInfo), { providerId, rawId }));
}
}
if (userInfo.phoneNumber) {
(0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(userInfo.phoneNumber), "phone number format is invalid");
fields.phoneNumber = userInfo.phoneNumber;
}
fields.validSince = (0, utils_1.toUnixTimestamp)(uploadTime).toString();
fields.createdAt = uploadTime.getTime().toString();
if (fields.createdAt && !isNaN(Number(userInfo.createdAt))) {
fields.createdAt = userInfo.createdAt;
}
if (userInfo.email) {
const email = userInfo.email;
(0, errors_1.assert)((0, utils_1.isValidEmailAddress)(email), "email is invalid");
const existingUserWithEmail = state.getUserByEmail(email);
(0, errors_1.assert)(!existingUserWithEmail || existingUserWithEmail.localId === userInfo.localId, reqBody.sanityCheck && state.oneAccountPerEmail
? "email exists in other account in database"
: `((Auth Emulator does not support importing duplicate email: ${email}))`);
fields.email = (0, utils_1.canonicalizeEmailAddress)(email);
}
fields.emailVerified = !!userInfo.emailVerified;
fields.disabled = !!userInfo.disabled;
if (userInfo.mfaInfo && userInfo.mfaInfo.length > 0) {
fields.mfaInfo = [];
(0, errors_1.assert)(fields.email, "Second factor account requires email to be presented.");
(0, errors_1.assert)(fields.emailVerified, "Second factor account requires email to be verified.");
const existingIds = new Set();
for (const enrollment of userInfo.mfaInfo) {
if (enrollment.mfaEnrollmentId) {
(0, errors_1.assert)(!existingIds.has(enrollment.mfaEnrollmentId), "Enrollment id already exists.");
existingIds.add(enrollment.mfaEnrollmentId);
}
}
for (const enrollment of userInfo.mfaInfo) {
enrollment.mfaEnrollmentId = enrollment.mfaEnrollmentId || newRandomId(28, existingIds);
enrollment.enrolledAt = enrollment.enrolledAt || new Date().toISOString();
(0, errors_1.assert)(enrollment.phoneInfo, "Second factor not supported.");
(0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(enrollment.phoneInfo), "Phone number format is invalid");
enrollment.unobfuscatedPhoneInfo = enrollment.phoneInfo;
fields.mfaInfo.push(enrollment);
}
}
if (state.getUserByLocalId(userInfo.localId)) {
(0, errors_1.assert)(reqBody.allowOverwrite, "localId belongs to an existing account - can not overwrite.");
}
state.overwriteUserWithLocalId(userInfo.localId, fields);
}
catch (e) {
if (e instanceof errors_1.BadRequestError) {
let message = e.message;
if (message === "INVALID_CLAIMS") {
message = "Invalid custom claims provided.";
}
else if (message === "CLAIMS_TOO_LARGE") {
message = "Custom claims provided are too large.";
}
else if (message.startsWith("FORBIDDEN_CLAIM")) {
message = "Custom claims provided include a reserved claim.";
}
errors.push({
index,
message,
});
}
else {
throw e;
}
}
}
return {
kind: "identitytoolkit#UploadAccountResponse",
error: errors,
};
}
function batchDelete(state, reqBody) {
var _a;
const errors = [];
const localIds = (_a = reqBody.localIds) !== null && _a !== void 0 ? _a : [];
(0, errors_1.assert)(localIds.length > 0 && localIds.length <= 1000, "LOCAL_ID_LIST_EXCEEDS_LIMIT");
for (let index = 0; index < localIds.length; index++) {
const localId = localIds[index];
const user = state.getUserByLocalId(localId);
if (!user) {
continue;
}
else if (!user.disabled && !reqBody.force) {
errors.push({
index,
localId,
message: "NOT_DISABLED : Disable the account before batch deletion.",
});
}
else {
state.deleteUser(user);
}
}
return { errors: errors.length ? errors : undefined };
}
function batchGet(state, reqBody, ctx) {
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
const maxResults = Math.min(Math.floor(ctx.params.query.maxResults) || 20, 1000);
const users = state.queryUsers({}, { sortByField: "localId", order: "ASC", startToken: ctx.params.query.nextPageToken });
let newPageToken = undefined;
if (maxResults >= 0 && users.length >= maxResults) {
users.length = maxResults;
if (users.length) {
newPageToken = users[users.length - 1].localId;
}
}
return {
kind: "identitytoolkit#DownloadAccountResponse",
users,
nextPageToken: newPageToken,
};
}
function createAuthUri(state, reqBody) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
const sessionId = reqBody.sessionId || (0, utils_1.randomId)(27);
if (reqBody.providerId) {
throw new errors_1.NotImplementedError("Sign-in with IDP is not yet supported.");
}
(0, errors_1.assert)(reqBody.identifier, "MISSING_IDENTIFIER");
(0, errors_1.assert)(reqBody.continueUri, "MISSING_CONTINUE_URI");
(0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.identifier), "INVALID_IDENTIFIER");
const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.identifier);
(0, errors_1.assert)((0, utils_1.parseAbsoluteUri)(reqBody.continueUri), "INVALID_CONTINUE_URI");
const allProviders = [];
const signinMethods = [];
let registered = false;
const users = state.getUsersByEmailOrProviderEmail(email);
if (state.oneAccountPerEmail) {
if (users.length) {
registered = true;
(_a = users[0].providerUserInfo) === null || _a === void 0 ? void 0 : _a.forEach(({ providerId }) => {
if (providerId === state_1.PROVIDER_PASSWORD) {
allProviders.push(providerId);
if (users[0].passwordHash) {
signinMethods.push(state_1.PROVIDER_PASSWORD);
}
if (users[0].emailLinkSignin) {
signinMethods.push(state_1.SIGNIN_METHOD_EMAIL_LINK);
}
}
else if (providerId !== state_1.PROVIDER_PHONE) {
allProviders.push(providerId);
signinMethods.push(providerId);
}
});
}
}
else {
const user = users.find((u) => u.email);
if (user) {
registered = true;
if (user.passwordHash || user.emailLinkSignin) {
allProviders.push(state_1.PROVIDER_PASSWORD);
if (users[0].passwordHash) {
signinMethods.push(state_1.PROVIDER_PASSWORD);
}
if (users[0].emailLinkSignin) {
signinMethods.push(state_1.SIGNIN_METHOD_EMAIL_LINK);
}
}
}
}
if (state.enableImprovedEmailPrivacy) {
return {
kind: "identitytoolkit#CreateAuthUriResponse",
sessionId,
};
}
else {
return {
kind: "identitytoolkit#CreateAuthUriResponse",
registered,
allProviders,
sessionId,
signinMethods,
};
}
}
const SESSION_COOKIE_MIN_VALID_DURATION = 5 * 60;
exports.SESSION_COOKIE_MAX_VALID_DURATION = 14 * 24 * 60 * 60;
function createSessionCookie(state, reqBody) {
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
const validDuration = Number(reqBody.validDuration) || exports.SESSION_COOKIE_MAX_VALID_DURATION;
(0, errors_1.assert)(validDuration >= SESSION_COOKIE_MIN_VALID_DURATION &&
validDuration <= exports.SESSION_COOKIE_MAX_VALID_DURATION, "INVALID_DURATION");
const { payload } = parseIdToken(state, reqBody.idToken);
const issuedAt = (0, utils_1.toUnixTimestamp)(new Date());
const expiresAt = issuedAt + validDuration;
const sessionCookie = (0, jsonwebtoken_1.sign)(Object.assign(Object.assign({}, payload), { iat: issuedAt, exp: expiresAt, iss: `https://session.firebase.google.com/${payload.aud}` }), "fake-secret", {
algorithm: "none",
});
return { sessionCookie };
}
function deleteAccount(state, reqBody, ctx) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
let user;
if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
(0, errors_1.assert)(reqBody.localId, "MISSING_LOCAL_ID");
const maybeUser = state.getUserByLocalId(reqBody.localId);
(0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
user = maybeUser;
}
else {
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
user = parseIdToken(state, reqBody.idToken).user;
}
state.deleteUser(user);
return {
kind: "identitytoolkit#DeleteAccountResponse",
};
}
function getProjects(state) {
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
return {
projectId: state.projectNumber,
authorizedDomains: [
"localhost",
],
};
}
function getRecaptchaParams(state) {
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
return {
kind: "identitytoolkit#GetRecaptchaParamResponse",
recaptchaStoken: "This-is-a-fake-token__Dont-send-this-to-the-Recaptcha-service__The-Auth-Emulator-does-not-support-Recaptcha",
recaptchaSiteKey: "Fake-key__Do-not-send-this-to-Recaptcha_",
};
}
function queryAccounts(state, reqBody) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
if ((_a = reqBody.expression) === null || _a === void 0 ? void 0 : _a.length) {
throw new errors_1.NotImplementedError("expression is not implemented.");
}
if (reqBody.returnUserInfo === false) {
return {
recordsCount: state.getUserCount().toString(),
};
}
if (reqBody.limit) {
throw new errors_1.NotImplementedError("limit is not implemented.");
}
reqBody.offset = reqBody.offset || "0";
if (reqBody.offset !== "0") {
throw new errors_1.NotImplementedError("offset is not implemented.");
}
if (!reqBody.order || reqBody.order === "ORDER_UNSPECIFIED") {
reqBody.order = "ASC";
}
if (!reqBody.sortBy || reqBody.sortBy === "SORT_BY_FIELD_UNSPECIFIED") {
reqBody.sortBy = "USER_ID";
}
let sortByField;
if (reqBody.sortBy === "USER_ID") {
sortByField = "localId";
}
else {
throw new errors_1.NotImplementedError("Only sorting by USER_ID is implemented.");
}
const users = state.queryUsers({}, { order: reqBody.order, sortByField });
return {
recordsCount: users.length.toString(),
userInfo: users,
};
}
function resetPassword(state, reqBody) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
(0, errors_1.assert)(reqBody.oobCode, "MISSING_OOB_CODE");
const oob = state.validateOobCode(reqBody.oobCode);
(0, errors_1.assert)(oob, "INVALID_OOB_CODE");
if (reqBody.newPassword) {
(0, errors_1.assert)(oob.requestType === "PASSWORD_RESET", "INVALID_OOB_CODE");
(0, errors_1.assert)(reqBody.newPassword.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters`);
state.deleteOobCode(reqBody.oobCode);
let user = state.getUserByEmail(oob.email);
(0, errors_1.assert)(user, "INVALID_OOB_CODE");
const salt = "fakeSalt" + (0, utils_1.randomId)(20);
const passwordHash = hashPassword(reqBody.newPassword, salt);
user = state.updateUserByLocalId(user.localId, {
emailVerified: true,
passwordHash,
salt,
passwordUpdatedAt: Date.now(),
validSince: (0, utils_1.toUnixTimestamp)(new Date()).toString(),
}, { deleteProviders: (_a = user.providerUserInfo) === null || _a === void 0 ? void 0 : _a.map((info) => info.providerId) });
}
return {
kind: "identitytoolkit#ResetPasswordResponse",
requestType: oob.requestType,
email: oob.requestType === "EMAIL_SIGNIN" ? undefined : oob.email,
newEmail: oob.newEmail,
};
}
exports.resetPassword = resetPassword;
function sendOobCode(state, reqBody, ctx) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(reqBody.requestType && reqBody.requestType !== "OOB_REQ_TYPE_UNSPECIFIED", "MISSING_REQ_TYPE");
if (reqBody.returnOobLink) {
(0, errors_1.assert)((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2, "INSUFFICIENT_PERMISSION");
}
if (reqBody.continueUrl) {
(0, errors_1.assert)((0, utils_1.parseAbsoluteUri)(reqBody.continueUrl), "INVALID_CONTINUE_URI : ((expected an absolute URI with valid scheme and host))");
}
let email;
let newEmail;
let mode;
switch (reqBody.requestType) {
case "EMAIL_SIGNIN":
(0, errors_1.assert)(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
mode = "signIn";
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
break;
case "PASSWORD_RESET":
mode = "resetPassword";
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
const maybeUser = state.getUserByEmail(email);
if (state.enableImprovedEmailPrivacy && !maybeUser) {
return {
kind: "identitytoolkit#GetOobConfirmationCodeResponse",
email,
};
}
(0, errors_1.assert)(maybeUser, "EMAIL_NOT_FOUND");
break;
case "VERIFY_EMAIL":
mode = "verifyEmail";
if (reqBody.returnOobLink && !reqBody.idToken) {
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
const maybeUser = state.getUserByEmail(email);
(0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
}
else {
const user = parseIdToken(state, reqBody.idToken || "").user;
(0, errors_1.assert)(user.email, "MISSING_EMAIL");
email = user.email;
}
break;
case "VERIFY_AND_CHANGE_EMAIL":
mode = "verifyAndChangeEmail";
(0, errors_1.assert)(reqBody.newEmail, "MISSING_NEW_EMAIL");
newEmail = (0, utils_1.canonicalizeEmailAddress)(reqBody.newEmail);
if (reqBody.returnOobLink && !reqBody.idToken) {
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
const maybeUser = state.getUserByEmail(email);
(0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
}
else {
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
const user = parseIdToken(state, reqBody.idToken).user;
(0, errors_1.assert)(user.email, "MISSING_EMAIL");
email = user.email;
}
(0, errors_1.assert)(!state.getUserByEmail(newEmail), "EMAIL_EXISTS");
break;
default:
throw new errors_1.NotImplementedError(reqBody.requestType);
}
if (reqBody.canHandleCodeInApp) {
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("WARN", "canHandleCodeInApp is unsupported in Auth Emulator. All OOB operations will complete via web.");
}
const url = (0, utils_1.authEmulatorUrl)(ctx.req);
const oobRecord = createOobRecord(state, email, url, {
requestType: reqBody.requestType,
mode,
continueUrl: reqBody.continueUrl,
newEmail,
});
if (reqBody.returnOobLink) {
return {
kind: "identitytoolkit#GetOobConfirmationCodeResponse",
email,
oobCode: oobRecord.oobCode,
oobLink: oobRecord.oobLink,
};
}
else {
logOobMessage(oobRecord);
return {
kind: "identitytoolkit#GetOobConfirmationCodeResponse",
email,
};
}
}
function sendVerificationCode(state, reqBody) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
(0, errors_1.assert)(reqBody.phoneNumber && (0, utils_1.isValidPhoneNumber)(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
const user = state.getUserByPhoneNumber(reqBody.phoneNumber);
(0, 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.");
const { sessionInfo, phoneNumber, code } = state.createVerificationCode(reqBody.phoneNumber);
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To verify the phone number ${phoneNumber}, use the code ${code}.`);
return {
sessionInfo,
};
}
function setAccountInfo(state, reqBody, ctx) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
const url = (0, utils_1.authEmulatorUrl)(ctx.req);
return setAccountInfoImpl(state, reqBody, {
privileged: !!((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2),
emulatorUrl: url,
});
}
function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl = undefined } = {}) {
var _a, _b;
const unimplementedFields = ["provider", "upgradeToFederatedLogin"];
for (const field of unimplementedFields) {
if (field in reqBody) {
throw new errors_1.NotImplementedError(`${field} is not implemented yet.`);
}
}
if (!privileged) {
(0, errors_1.assert)(reqBody.idToken || reqBody.oobCode, "INVALID_REQ_TYPE : Unsupported request parameters.");
(0, errors_1.assert)(reqBody.customAttributes == null, "INSUFFICIENT_PERMISSION");
}
else {
(0, errors_1.assert)(reqBody.localId, "MISSING_LOCAL_ID");
}
if (reqBody.customAttributes) {
validateSerializedCustomClaims(reqBody.customAttributes);
}
reqBody.deleteAttribute = reqBody.deleteAttribute || [];
for (const attr of reqBody.deleteAttribute) {
if (attr === "PROVIDER" || attr === "RAW_USER_INFO") {
throw new errors_1.NotImplementedError(`deleteAttribute: ${attr}`);
}
}
const updates = {};
let user;
let signInProvider;
let isEmailUpdate = false;
let newEmail;
if (reqBody.oobCode) {
const oob = state.validateOobCode(reqBody.oobCode);
(0, errors_1.assert)(oob, "INVALID_OOB_CODE");
switch (oob.requestType) {
case "VERIFY_EMAIL": {
state.deleteOobCode(reqBody.oobCode);
signInProvider = state_1.PROVIDER_PASSWORD;
const maybeUser = state.getUserByEmail(oob.email);
(0, errors_1.assert)(maybeUser, "INVALID_OOB_CODE");
user = maybeUser;
updates.emailVerified = true;
if (oob.email !== user.email) {
updates.email = oob.email;
}
break;
}
case "VERIFY_AND_CHANGE_EMAIL":
state.deleteOobCode(reqBody.oobCode);
const maybeUser = state.getUserByEmail(oob.email);
(0, errors_1.assert)(maybeUser, "INVALID_OOB_CODE");
(0, errors_1.assert)(oob.newEmail, "INVALID_OOB_CODE");
(0, errors_1.assert)(!state.getUserByEmail(oob.newEmail), "EMAIL_EXISTS");
user = maybeUser;
if (oob.newEmail !== user.email) {
updates.email = oob.newEmail;
updates.emailVerified = true;
newEmail = oob.newEmail;
}
break;
case "RECOVER_EMAIL": {
state.deleteOobCode(reqBody.oobCode);
const maybeUser = state.getUserByInitialEmail(oob.email);
(0, errors_1.assert)(maybeUser, "INVALID_OOB_CODE");
(0, errors_1.assert)(!state.getUserByEmail(oob.email), "EMAIL_EXISTS");
user = maybeUser;
if (oob.email !== user.email) {
updates.email = oob.email;
updates.emailVerified = true;
}
break;
}
default:
throw new errors_1.NotImplementedError(oob.requestType);
}
}
else {
if (reqBody.idToken) {
({ user, signInProvider } = parseIdToken(state, reqBody.idToken));
(0, errors_1.assert)(reqBody.disableUser == null, "OPERATION_NOT_ALLOWED");
}
else {
(0, errors_1.assert)(reqBody.localId, "MISSING_LOCAL_ID");
const maybeUser = state.getUserByLocalId(reqBody.localId);
(0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
user = maybeUser;
}
if (reqBody.email) {
(0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
newEmail = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
if (newEmail !== user.email) {
(0, errors_1.assert)(!state.getUserByEmail(newEmail), "EMAIL_EXISTS");
updates.email = newEmail;
updates.emailVerified = false;
isEmailUpdate = true;
if (signInProvider !== state_1.PROVIDER_ANONYMOUS && user.email && !user.initialEmail) {
updates.initialEmail = user.email;
}
}
}
if (reqBody.password) {
(0, errors_1.assert)(reqBody.password.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters`);
updates.salt = "fakeSalt" + (0, utils_1.randomId)(20);
updates.passwordHash = hashPassword(reqBody.password, updates.salt);
updates.passwordUpdatedAt = Date.now();
signInProvider = state_1.PROVIDER_PASSWORD;
}
if (reqBody.password || reqBody.validSince || updates.email) {
updates.validSince = (0, utils_1.toUnixTimestamp)(new Date()).toString();
}
if (reqBody.mfa) {
if (reqBody.mfa.enrollments && reqBody.mfa.enrollments.length > 0) {
updates.mfaInfo = getMfaEnrollmentsFromRequest(state, reqBody.mfa.enrollments);
}
else {
updates.mfaInfo = undefined;
}
}
const fieldsToCopy = [
"displayName",
"photoUrl",
];
if (privileged) {
if (reqBody.disableUser != null) {
updates.disabled = reqBody.disableUser;
}
if (reqBody.phoneNumber && reqBody.phoneNumber !== user.phoneNumber) {
(0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
(0, errors_1.assert)(!state.getUserByPhoneNumber(reqBody.phoneNumber), "PHONE_NUMBER_EXISTS");
updates.phoneNumber = reqBody.phoneNumber;
}
fieldsToCopy.push("emailVerified", "customAttributes", "createdAt", "lastLoginAt", "validSince");
}
for (const field of fieldsToCopy) {
if (reqBody[field] != null) {
(0, utils_1.mirrorFieldTo)(updates, field, reqBody);
}
}
for (const attr of reqBody.deleteAttribute) {
switch (attr) {
case "USER_ATTRIBUTE_NAME_UNSPECIFIED":
continue;
case "DISPLAY_NAME":
updates.displayName = undefined;
break;
case "PHOTO_URL":
updates.photoUrl = undefined;
break;
case "PASSWORD":
updates.passwordHash = undefined;
updates.salt = undefined;
break;
case "EMAIL":
updates.email = undefined;
updates.emailVerified = undefined;
updates.emailLinkSignin = undefined;
break;
}
}
if ((_a = reqBody.deleteProvider) === null || _a === void 0 ? void 0 : _a.includes(state_1.PROVIDER_PASSWORD)) {
updates.email = undefined;
updates.emailVerified = undefined;
updates.emailLinkSignin = undefined;
updates.passwordHash = undefined;
updates.salt = undefined;
}
if ((_b = reqBody.deleteProvider) === null || _b === void 0 ? void 0 : _b.includes(state_1.PROVIDER_PHONE)) {
updates.phoneNumber = undefined;
}
}
if (reqBody.linkProviderUserInfo) {
(0, errors_1.assert)(reqBody.linkProviderUserInfo.providerId, "MISSING_PROVIDER_ID");
(0, errors_1.assert)(reqBody.linkProviderUserInfo.rawId, "MISSING_RAW_ID");
}
user = state.updateUserByLocalId(user.localId, updates, {
deleteProviders: reqBody.deleteProvider,
upsertProviders: reqBody.linkProviderUserInfo
? [reqBody.linkProviderUserInfo]
: undefined,
});
if (signInProvider !== state_1.PROVIDER_ANONYMOUS && user.initialEmail && isEmailUpdate) {
if (!emulatorUrl) {
throw new Error("Internal assertion error: missing emulatorUrl param");
}
sendOobForEmailReset(state, user.initialEmail, emulatorUrl);
}
return redactPasswordHash(Object.assign({ kind: "identitytoolkit#SetAccountInfoResponse", localId: user.localId, emailVerified: user.emailVerified, providerUserInfo: user.providerUserInfo, email: user.email, displayName: user.displayName, photoUrl: user.photoUrl, newEmail, passwordHash: user.passwordHash }, (updates.validSince && signInProvider ? issueTokens(state, user, signInProvider) : {})));
}
exports.setAccountInfoImpl = setAccountInfoImpl;
function sendOobForEmailReset(state, initialEmail, url) {
const oobRecord = createOobRecord(state, initialEmail, url, {
requestType: "RECOVER_EMAIL",
mode: "recoverEmail",
});
logOobMessage(oobRecord);
}
function createOobRecord(state, email, url, params) {
const oobRecord = state.createOob(email, params.newEmail, params.requestType, (oobCode) => {
url.pathname = "/emulator/action";
url.searchParams.set("mode", params.mode);
url.searchParams.set("lang", "en");
url.searchParams.set("oobCode", oobCode);
url.searchParams.set("apiKey", "fake-api-key");
if (params.continueUrl) {
url.searchParams.set("continueUrl", params.continueUrl);
}
if (state instanceof state_1.TenantProjectState) {
url.searchParams.set("tenantId", state.tenantId);
}
return url.toString();
});
return oobRecord;
}
function logOobMessage(oobRecord) {
const oobLink = oobRecord.oobLink;
const email = oobRecord.email;
let maybeMessage;
switch (oobRecord.requestType) {
case "EMAIL_SIGNIN":
maybeMessage = `To sign in as ${email}, follow this link: ${oobLink}`;
break;
case "PASSWORD_RESET":
maybeMessage = `To reset the password for ${email}, follow this link: ${oobLink}&newPassword=NEW_PASSWORD_HERE`;
break;
case "VERIFY_EMAIL":
maybeMessage = `To verify the email address ${email}, follow this link: ${oobLink}`;
break;
case "VERIFY_AND_CHANGE_EMAIL":
maybeMessage = `To verify and change the email address from ${email} to ${oobRecord.newEmail}, follow this link: ${oobLink}`;
break;
case "RECOVER_EMAIL":
maybeMessage = `To reset your email address to ${email}, follow this link: ${oobLink}`;
break;
}
if (maybeMessage) {
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", maybeMessage);
}
}
function signInWithCustomToken(state, reqBody) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(reqBody.token, "MISSING_CUSTOM_TOKEN");
let payload;
if (reqBody.token.startsWith("{")) {
try {
payload = JSON.parse(reqBody.token);
}
catch (_b) {
throw new errors_1.BadRequestError("INVALID_CUSTOM_TOKEN : ((Auth Emulator only accepts strict JSON or JWTs as fake custom tokens.))");
}
}
else {
const decoded = (0, jsonwebtoken_1.decode)(reqBody.token, { complete: true });
if (state instanceof state_1.TenantProjectState) {
(0, errors_1.assert)((decoded === null || decoded === void 0 ? void 0 : decoded.payload.tenant_id) === state.tenantId, "TENANT_ID_MISMATCH");
}
(0, errors_1.assert)(decoded, "INVALID_CUSTOM_TOKEN : Invalid assertion format");
if (decoded.header.alg !== "none") {
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");
}
(0, errors_1.assert)(decoded.payload.aud === exports.CUSTOM_TOKEN_AUDIENCE, `INVALID_CUSTOM_TOKEN : ((Invalid aud (audience): ${decoded.payload.aud} ` +
"Note: Firebase ID Tokens / third-party tokens cannot be used with signInWithCustomToken.))");
payload = decoded.payload;
}
const localId = (_a = coercePrimitiveToString(payload.uid)) !== null && _a !== void 0 ? _a : coercePrimitiveToString(payload.user_id);
(0, errors_1.assert)(localId, "MISSING_IDENTIFIER");
let extraClaims = {};
if ("claims" in payload) {
validateCustomClaims(payload.claims);
extraClaims = payload.claims;
}
let user = state.getUserByLocalId(localId);
const isNewUser = !user;
const timestamp = new Date();
const updates = {
customAuth: true,
lastLoginAt: timestamp.getTime().toString(),
tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined,
};
if (user) {
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
user = state.updateUserByLocalId(localId, updates);
}
else {
updates.createdAt = timestamp.getTime().toString();
user = state.createUserWithLocalId(localId, updates);
if (!user) {
throw new Error(`Internal assertion error: trying to create duplicate localId: ${localId}`);
}
}
return Object.assign({ kind: "identitytoolkit#VerifyCustomTokenResponse", isNewUser }, issueTokens(state, user, state_1.PROVIDER_CUSTOM, { extraClaims }));
}
async function signInWithEmailLink(state, reqBody) {
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
(0, errors_1.assert)(reqBody.oobCode, "MISSING_OOB_CODE");
const oob = state.validateOobCode(reqBody.oobCode);
(0, errors_1.assert)(oob && oob.requestType === "EMAIL_SIGNIN", "INVALID_OOB_CODE");
(0, errors_1.assert)(email === oob.email, "INVALID_EMAIL : The email provided does not match the sign-in email address.");
state.deleteOobCode(reqBody.oobCode);
const userFromEmail = state.getUserByEmail(email);
let user = userFromIdToken || userFromEmail;
const isNewUser = !user;
const timestamp = new Date();
let updates = {
email,
emailVerified: true,
emailLinkSignin: true,
};
if (state instanceof state_1.TenantProjectState) {
updates.tenantId = state.tenantId;
}
let extraClaims;
if (!user) {
updates.createdAt = timestamp.getTime().toString();
const localId = state.generateLocalId();
const userBeforeCreate = Object.assign({ localId }, updates);
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, { signInMethod: "emailLink" });
updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
user = state.createUserWithLocalId(localId, updates);
if (!user.disabled && !isMfaEnabled(state, user)) {
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "emailLink" });
updates = blockingResponse.updates;
extraClaims = blockingResponse.extraClaims;
user = state.updateUserByLocalId(user.localId, updates);
}
}
else {
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
if (userFromIdToken && userFromEmail) {
(0, errors_1.assert)(userFromIdToken.localId === userFromEmail.localId, "EMAIL_EXISTS");
}
if (!user.disabled && !isMfaEnabled(state, user)) {
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, Object.assign(Object.assign({}, user), updates), { signInMethod: "emailLink" });
updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
extraClaims = blockingResponse.extraClaims;
}
user = state.updateUserByLocalId(user.localId, updates);
}
const response = {
kind: "identitytoolkit#EmailLinkSigninResponse",
email,
localId: user.localId,
isNewUser,
};
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
if (isMfaEnabled(state, user)) {
return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
}
else {
user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
return Object.assign(Object.assign({}, response), issueTokens(state, user, state_1.PROVIDER_PASSWORD, { extraClaims }));
}
}
async function signInWithIdp(state, reqBody) {
var _a, _b;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
if (reqBody.returnRefreshToken) {
throw new errors_1.NotImplementedError("returnRefreshToken is not implemented yet.");
}
if (reqBody.pendingIdToken) {
throw new errors_1.NotImplementedError("pendingIdToken is not implemented yet.");
}
const normalizedUri = getNormalizedUri(reqBody);
const providerId = (_a = normalizedUri.searchParams.get("providerId")) === null || _a === void 0 ? void 0 : _a.toLowerCase();
(0, errors_1.assert)(providerId, `INVALID_CREDENTIAL_OR_PROVIDER_ID : Invalid IdP response/credential: ${normalizedUri.toString()}`);
const oauthIdToken = normalizedUri.searchParams.get("id_token") || undefined;
const oauthAccessToken = normalizedUri.searchParams.get("access_token") || undefined;
const claims = parseClaims(oauthIdToken) || parseClaims(oauthAccessToken);
if (!claims) {
if (oauthIdToken) {
throw new errors_1.BadRequestError(`INVALID_IDP_RESPONSE : Unable to parse id_token: ${oauthIdToken} ((Auth Emulator only accepts strict JSON or JWTs as fake id_tokens.))`);
}
else if (oauthAccessToken) {
if (providerId === "google.com" || providerId === "apple.com") {
throw new errors_1.NotImplementedError(`The Auth Emulator only support sign-in with ${providerId} using id_token, not access_token. Please update your code to use id_token.`);
}
else {
throw new errors_1.NotImplementedError(`The Auth Emulator does not support ${providerId} sign-in with credentials.`);
}
}
else {
throw new errors_1.NotImplementedError("The Auth Emulator only supports sign-in with credentials (id_token required).");
}
}
let samlResponse;
let signInAttributes = undefined;
if (normalizedUri.searchParams.get("SAMLResponse")) {
samlResponse = JSON.parse(normalizedUri.searchParams.get("SAMLResponse"));
signInAttributes = (_b = samlResponse.assertion) === null || _b === void 0 ? void 0 : _b.attributeStatements;
(0, errors_1.assert)(samlResponse.assertion, "INVALID_IDP_RESPONSE ((Missing assertion in SAMLResponse.))");
(0, errors_1.assert)(samlResponse.assertion.subject, "INVALID_IDP_RESPONSE ((Missing assertion.subject in SAMLResponse.))");
(0, errors_1.assert)(samlResponse.assertion.subject.nameId, "INVALID_IDP_RESPONSE ((Missing assertion.subject.nameId in SAMLResponse.))");
}
let { response, rawId } = fakeFetchUserInfoFromIdp(providerId, claims, samlResponse);
response.oauthAccessToken =
oauthAccessToken || `FirebaseAuthEmulatorFakeAccessToken_${providerId}`;
response.oauthIdToken = oauthIdToken;
const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
const userMatchingProvider = state.getUserByProviderRawId(providerId, rawId);
let accountUpdates;
try {
if (userFromIdToken) {
(0, errors_1.assert)(!userMatchingProvider, "FEDERATED_USER_ID_ALREADY_LINKED");
({ accountUpdates, response } = handleLinkIdp(state, response, userFromIdToken));
}
else if (state.oneAccountPerEmail) {
const userMatchingEmail = response.email ? state.getUserByEmail(response.email) : undefined;
({ accountUpdates, response } = handleIdpSigninEmailRequired(response, rawId, userMatchingProvider, userMatchingEmail));
}
else {
({ accountUpdates, response } = handleIdpSigninEmailNotRequired(response, userMatchingProvider));
}
}
catch (err) {
if (reqBody.returnIdpCredential && err instanceof errors_1.BadRequestError) {
response.errorMessage = err.message;
return response;
}
else {
throw err;
}
}
if (response.needConfirmation) {
return response;
}
const providerUserInfo = {
providerId,
rawId,
federatedId: rawId,
displayName: response.displayName,
photoUrl: response.photoUrl,
email: response.email,
screenName: response.screenName,
};
let user;
let extraClaims;
const oauthTokens = {
oauthIdToken: response.oauthIdToken,
oauthAccessToken: response.oauthAccessToken,
oauthRefreshToken: response.oauthRefreshToken,
oauthTokenSecret: response.oauthTokenSecret,
oauthExpiresIn: coercePrimitiveToString(response.oauthExpireIn),
};
if (response.isNewUser) {
const timestamp = new Date();
let updates = Object.assign(Object.assign({}, accountUpdates.fields), { createdAt: timestamp.getTime().toString(), lastLoginAt: timestamp.getTime().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined });
const localId = state.generateLocalId();
const userBeforeCreate = Object.assign({ localId }, updates);
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, {
signInMethod: response.providerId,
rawUserInfo: response.rawUserInfo,
signInAttributes: JSON.stringify(signInAttributes),
}, oauthTokens);
updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
user = state.createUserWithLocalId(localId, updates);
response.localId = user.localId;
if (!user.disabled && !isMfaEnabled(state, user)) {
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, {
signInMethod: response.providerId,
rawUserInfo: response.rawUserInfo,
signInAttributes: JSON.stringify(signInAttributes),
}, oauthTokens);
updates = blockingResponse.updates;
extraClaims = blockingResponse.extraClaims;
user = state.updateUserByLocalId(user.localId, updates);
}
}
else {
if (!response.localId) {
throw new Error("Internal assertion error: localId not set for existing user.");
}
const maybeUser = state.getUserByLocalId(response.localId);
(0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
user = maybeUser;
let updates = Object.assign({}, accountUpdates.fields);
if (!user.disabled && !isMfaEnabled(state, user)) {
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, Object.assign(Object.assign({}, user), updates), {
signInMethod: response.providerId,
rawUserInfo: response.rawUserInfo,
signInAttributes: JSON.stringify(signInAttributes),
}, oauthTokens);
extraClaims = blockingResponse.extraClaims;
updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
}
user = state.updateUserByLocalId(response.localId, updates, {
upsertProviders: [providerUserInfo],
});
}
if (user.email === response.email) {
response.emailVerified = user.emailVerified;
}
if (state instanceof state_1.TenantProjectState) {
response.tenantId = state.tenantId;
}
if (isMfaEnabled(state, user)) {
return Object.assign(Object.assign({}, response), mfaPending(state, user, providerId));
}
else {
user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
(0, errors_1.assert)(!(user === null || user === void 0 ? void 0 : user.disabled), "USER_DISABLED");
return Object.assign(Object.assign({}, response), issueTokens(state, user, providerId, { signInAttributes, extraClaims }));
}
}
async function signInWithPassword(state, reqBody) {
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
(0, errors_1.assert)(reqBody.email !== undefined, "MISSING_EMAIL");
(0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
(0, errors_1.assert)(reqBody.password, "MISSING_PASSWORD");
if (reqBody.captchaResponse || reqBody.captchaChallenge) {
throw new errors_1.NotImplementedError("captcha unimplemented");
}
if (reqBody.idToken || reqBody.pendingIdToken) {
throw new errors_1.NotImplementedError("idToken / pendingIdToken is no longer in use and unsupported by the Auth Emulator.");
}
const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
let user = state.getUserByEmail(email);
if (state.enableImprovedEmailPrivacy) {
(0, errors_1.assert)(user, "INVALID_LOGIN_CREDENTIALS");
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
(0, errors_1.assert)(user.passwordHash && user.salt, "INVALID_LOGIN_CREDENTIALS");
(0, errors_1.assert)(user.passwordHash === hashPassword(reqBody.password, user.salt), "INVALID_LOGIN_CREDENTIALS");
}
else {
(0, errors_1.assert)(user, "EMAIL_NOT_FOUND");
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
(0, errors_1.assert)(user.passwordHash && user.salt, "INVALID_PASSWORD");
(0, errors_1.assert)(user.passwordHash === hashPassword(reqBody.password, user.salt), "INVALID_PASSWORD");
}
const response = {
kind: "identitytoolkit#VerifyPasswordResponse",
registered: true,
localId: user.localId,
email,
};
if (isMfaEnabled(state, user)) {
return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
}
else {
const { updates, extraClaims } = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "password" });
user = state.updateUserByLocalId(user.localId, Object.assign(Object.assign({}, updates), { lastLoginAt: Date.now().toString() }));
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
return Object.assign(Object.assign({}, response), issueTokens(state, user, state_1.PROVIDER_PASSWORD, { extraClaims }));
}
}
async function signInWithPhoneNumber(state, reqBody) {
var _a;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
let phoneNumber;
if (reqBody.temporaryProof) {
(0, errors_1.assert)(reqBody.phoneNumber, "MISSING_PHONE_NUMBER");
const proof = state.validateTemporaryProof(reqBody.temporaryProof, reqBody.phoneNumber);
(0, errors_1.assert)(proof, "INVALID_TEMPORARY_PROOF");
({ phoneNumber } = proof);
}
else {
(0, errors_1.assert)(reqBody.sessionInfo, "MISSING_SESSION_INFO");
(0, errors_1.assert)(reqBody.code, "MISSING_CODE");
phoneNumber = verifyPhoneNumber(state, reqBody.sessionInfo, reqBody.code);
}
const userFromPhoneNumber = state.getUserByPhoneNumber(phoneNumber);
const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
if (userFromPhoneNumber && userFromIdToken) {
if (userFromPhoneNumber.localId !== userFromIdToken.localId) {
(0, errors_1.assert)(!reqBody.temporaryProof, "PHONE_NUMBER_EXISTS");
return Object.assign({}, state.createTemporaryProof(phoneNumber));
}
}
let user = userFromIdToken || userFromPhoneNumber;
const isNewUser = !user;
const timestamp = new Date();
let updates = {
phoneNumber,
lastLoginAt: timestamp.getTime().toString(),
};
let extraClaims;
if (!user) {
updates.createdAt = timestamp.getTime().toString();
const localId = state.generateLocalId();
const userBeforeCreate = Object.assign({ localId }, updates);
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, { signInMethod: "phone" });
updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
user = state.createUserWithLocalId(localId, updates);
if (!user.disabled) {
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "phone" });
updates = blockingResponse.updates;
extraClaims = blockingResponse.extraClaims;
user = state.updateUserByLocalId(user.localId, updates);
}
}
else {
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
(0, errors_1.assert)(!((_a = 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.");
if (!user.disabled) {
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, Object.assign(Object.assign({}, user), updates), { signInMethod: "phone" });
updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
extraClaims = blockingResponse.extraClaims;
}
user = state.updateUserByLocalId(user.localId, updates);
}
(0, errors_1.assert)(!(user === null || user === void 0 ? void 0 : user.disabled), "USER_DISABLED");
const tokens = issueTokens(state, user, state_1.PROVIDER_PHONE, {
extraClaims,
});
return Object.assign({ isNewUser,
phoneNumber, localId: user.localId }, tokens);
}
function grantToken(state, reqBody) {
(0, errors_1.assert)(reqBody.grantType, "MISSING_GRANT_TYPE");
(0, errors_1.assert)(reqBody.grantType === "refresh_token", "INVALID_GRANT_TYPE");
(0, errors_1.assert)(reqBody.refreshToken, "MISSING_REFRESH_TOKEN");
const refreshTokenRecord = state.validateRefreshToken(reqBody.refreshToken);
(0, errors_1.assert)(!refreshTokenRecord.user.disabled, "USER_DISABLED");
const tokens = issueTokens(state, refreshTokenRecord.user, refreshTokenRecord.provider, {
extraClaims: refreshTokenRecord.extraClaims,
secondFactor: refreshTokenRecord.secondFactor,
});
return {
id_token: tokens.idToken,
access_token: tokens.idToken,
expires_in: tokens.expiresIn,
refresh_token: tokens.refreshToken,
token_type: "Bearer",
user_id: refreshTokenRecord.user.localId,
project_id: state.projectNumber,
};
}
function deleteAllAccountsInProject(state) {
state.deleteAllAccounts();
return {};
}
function getEmulatorProjectConfig(state) {
return {
signIn: {
allowDuplicateEmails: !state.oneAccountPerEmail,
},
emailPrivacyConfig: {
enableImprovedEmailPrivacy: state.enableImprovedEmailPrivacy,
},
};
}
function updateEmulatorProjectConfig(state, reqBody, ctx) {
var _a, _b;
const updateMask = [];
if (((_a = reqBody.signIn) === null || _a === void 0 ? void 0 : _a.allowDuplicateEmails) != null) {
updateMask.push("signIn.allowDuplicateEmails");
}
if (((_b = reqBody.emailPrivacyConfig) === null || _b === void 0 ? void 0 : _b.enableImprovedEmailPrivacy) != null) {
updateMask.push("emailPrivacyConfig.enableImprovedEmailPrivacy");
}
ctx.params.query.updateMask = updateMask.join();
updateConfig(state, reqBody, ctx);
return getEmulatorProjectConfig(state);
}
function listOobCodesInProject(state) {
return {
oobCodes: [...state.listOobCodes()],
};
}
function listVerificationCodesInProject(state) {
return {
verificationCodes: [...state.listVerificationCodes()],
};
}
function mfaEnrollmentStart(state, reqBody) {
var _a, _b;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, 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.");
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
const { user, signInProvider } = parseIdToken(state, reqBody.idToken);
(0, errors_1.assert)(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
(0, errors_1.assert)(user.emailVerified, "UNVERIFIED_EMAIL : Need to verify email first before enrolling second factors.");
(0, errors_1.assert)(reqBody.phoneEnrollmentInfo, "INVALID_ARGUMENT : ((Missing phoneEnrollmentInfo.))");
const phoneNumber = reqBody.phoneEnrollmentInfo.phoneNumber;
(0, errors_1.assert)(phoneNumber && (0, utils_1.isValidPhoneNumber)(phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
(0, 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.");
const { sessionInfo, code } = state.createVerificationCode(phoneNumber);
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To enroll MFA with ${phoneNumber}, use the code ${code}.`);
return {
phoneSessionInfo: {
sessionInfo,
},
};
}
function mfaEnrollmentFinalize(state, reqBody) {
var _a, _b;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, 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.");
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
(0, errors_1.assert)(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
(0, errors_1.assert)(reqBody.phoneVerificationInfo, "INVALID_ARGUMENT : ((Missing phoneVerificationInfo.))");
if (reqBody.phoneVerificationInfo.androidVerificationProof) {
throw new errors_1.NotImplementedError("androidVerificationProof is unsupported!");
}
const { code, sessionInfo } = reqBody.phoneVerificationInfo;
(0, errors_1.assert)(code, "MISSING_CODE");
(0, errors_1.assert)(sessionInfo, "MISSING_SESSION_INFO");
const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
(0, 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.");
const existingFactors = user.mfaInfo || [];
const existingIds = new Set();
for (const { mfaEnrollmentId } of existingFactors) {
if (mfaEnrollmentId) {
existingIds.add(mfaEnrollmentId);
}
}
const enrollment = {
displayName: reqBody.displayName,
enrolledAt: new Date().toISOString(),
mfaEnrollmentId: newRandomId(28, existingIds),
phoneInfo: phoneNumber,
unobfuscatedPhoneInfo: phoneNumber,
};
user = state.updateUserByLocalId(user.localId, {
mfaInfo: [...existingFactors, enrollment],
});
const { idToken, refreshToken } = issueTokens(state, user, signInProvider, {
secondFactor: { identifier: enrollment.mfaEnrollmentId, provider: state_1.PROVIDER_PHONE },
});
return {
idToken,
refreshToken,
};
}
function mfaEnrollmentWithdraw(state, reqBody) {
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
(0, errors_1.assert)(user.mfaInfo, "MFA_ENROLLMENT_NOT_FOUND");
const updatedList = user.mfaInfo.filter((enrollment) => enrollment.mfaEnrollmentId !== reqBody.mfaEnrollmentId);
(0, errors_1.assert)(updatedList.length < user.mfaInfo.length, "MFA_ENROLLMENT_NOT_FOUND");
user = state.updateUserByLocalId(user.localId, { mfaInfo: updatedList });
return Object.assign({}, issueTokens(state, user, signInProvider));
}
function mfaSignInStart(state, reqBody) {
var _a, _b;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, 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.");
(0, errors_1.assert)(reqBody.mfaPendingCredential, "MISSING_MFA_PENDING_CREDENTIAL : Request does not have MFA pending credential.");
(0, errors_1.assert)(reqBody.mfaEnrollmentId, "MISSING_MFA_ENROLLMENT_ID : No second factor identifier is provided.");
const { user } = parsePendingCredential(state, reqBody.mfaPendingCredential);
const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((factor) => factor.mfaEnrollmentId === reqBody.mfaEnrollmentId);
(0, errors_1.assert)(enrollment, "MFA_ENROLLMENT_NOT_FOUND");
const phoneNumber = enrollment.unobfuscatedPhoneInfo;
(0, errors_1.assert)(phoneNumber, "INVALID_ARGUMENT : MFA provider not supported!");
const { sessionInfo, code } = state.createVerificationCode(phoneNumber);
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To sign in with MFA using ${phoneNumber}, use the code ${code}.`);
return {
phoneResponseInfo: {
sessionInfo,
},
};
}
async function mfaSignInFinalize(state, reqBody) {
var _a, _b;
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
(0, 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.");
(0, errors_1.assert)(reqBody.mfaPendingCredential, "MISSING_CREDENTIAL : Please set MFA Pending Credential.");
(0, errors_1.assert)(reqBody.phoneVerificationInfo, "INVALID_ARGUMENT : MFA provider not supported!");
if (reqBody.phoneVerificationInfo.androidVerificationProof) {
throw new errors_1.NotImplementedError("androidVerificationProof is unsupported!");
}
const { code, sessionInfo } = reqBody.phoneVerificationInfo;
(0, errors_1.assert)(code, "MISSING_CODE");
(0, errors_1.assert)(sessionInfo, "MISSING_SESSION_INFO");
const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
let { user, signInProvider } = parsePendingCredential(state, reqBody.mfaPendingCredential);
const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber);
const { updates, extraClaims } = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: signInProvider, signInSecondFactor: "phone" });
user = state.updateUserByLocalId(user.localId, Object.assign(Object.assign({}, updates), { lastLoginAt: Date.now().toString() }));
(0, errors_1.assert)(enrollment && enrollment.mfaEnrollmentId, "MFA_ENROLLMENT_NOT_FOUND");
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
const { idToken, refreshToken } = issueTokens(state, user, signInProvider, {
extraClaims,
secondFactor: { identifier: enrollment.mfaEnrollmentId, provider: state_1.PROVIDER_PHONE },
});
return {
idToken,
refreshToken,
};
}
function getConfig(state) {
(0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Can only get top-level configurations on agent projects.))");
return state.config;
}
function updateConfig(state, reqBody, ctx) {
var _a;
(0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Can only update top-level configurations on agent projects.))");
for (const event in (_a = reqBody.blockingFunctions) === null || _a === void 0 ? void 0 : _a.triggers) {
if (Object.prototype.hasOwnProperty.call(reqBody.blockingFunctions.triggers, event)) {
(0, errors_1.assert)(Object.values(state_1.BlockingFunctionEvents).includes(event), "INVALID_BLOCKING_FUNCTION : ((Event type is invalid.))");
(0, errors_1.assert)((0, utils_1.parseAbsoluteUri)(reqBody.blockingFunctions.triggers[event].functionUri), "INVALID_BLOCKING_FUNCTION : ((Expected an absolute URI with valid scheme and host.))");
}
}
return state.updateConfig(reqBody, ctx.params.query.updateMask);
}
function coercePrimitiveToString(value) {
switch (typeof value) {
case "string":
return value;
case "number":
case "boolean":
return value.toString();
default:
return undefined;
}
}
function redactPasswordHash(user) {
return user;
}
function hashPassword(password, salt) {
return `fakeHash:salt=${salt}:password=${password}`;
}
function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, signInAttributes, } = {}) {
user = state.updateUserByLocalId(user.localId, { lastRefreshAt: new Date().toISOString() });
const tenantId = state instanceof state_1.TenantProjectState ? state.tenantId : undefined;
const expiresInSeconds = 60 * 60;
const idToken = generateJwt(user, {
projectId: state.projectId,
signInProvider,
expiresInSeconds,
extraClaims,
secondFactor,
tenantId,
signInAttributes,
});
const refreshToken = state.createRefreshTokenFor(user, signInProvider, {
extraClaims,
secondFactor,
});
return {
idToken,
refreshToken,
expiresIn: expiresInSeconds.toString(),
};
}
function parseIdToken(state, idToken) {
const decoded = (0, jsonwebtoken_1.decode)(idToken, { complete: true });
(0, errors_1.assert)(decoded, "INVALID_ID_TOKEN");
if (decoded.header.alg !== "none") {
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("WARN", "Received a signed JWT. Auth Emulator does not validate JWTs and IS NOT SECURE");
}
if (decoded.payload.firebase.tenant) {
(0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Parsed token that belongs to tenant in a non-tenant project.))");
(0, errors_1.assert)(decoded.payload.firebase.tenant === state.tenantId, "TENANT_ID_MISMATCH");
}
const user = state.getUserByLocalId(decoded.payload.user_id);
(0, errors_1.assert)(user, "USER_NOT_FOUND");
(0, errors_1.assert)(!user.validSince || decoded.payload.iat >= Number(user.validSince), "TOKEN_EXPIRED");
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
const signInProvider = decoded.payload.firebase.sign_in_provider;
return { user, signInProvider, payload: decoded.payload };
}
function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, tenantId, signInAttributes, }) {
const identities = {};
if (user.email) {
identities["email"] = [user.email];
}
if (user.providerUserInfo) {
for (const providerInfo of user.providerUserInfo) {
if (providerInfo.providerId &&
providerInfo.providerId !== state_1.PROVIDER_PASSWORD &&
providerInfo.rawId) {
const ids = identities[providerInfo.providerId] || [];
ids.push(providerInfo.rawId);
identities[providerInfo.providerId] = ids;
}
}
}
const customAttributes = JSON.parse(user.customAttributes || "{}");
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: (0, utils_1.toUnixTimestamp)(getAuthTime(user)), user_id: user.localId, firebase: {
identities,
sign_in_provider: signInProvider,
second_factor_identifier: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.identifier,
sign_in_second_factor: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.provider,
tenant: tenantId,
sign_in_attributes: signInAttributes,
} });
const jwtStr = (0, jsonwebtoken_1.sign)(customPayloadFields, "fake-secret", {
algorithm: "none",
expiresIn: expiresInSeconds,
subject: user.localId,
issuer: `https://securetoken.google.com/${projectId}`,
audience: projectId,
});
return jwtStr;
}
function getAuthTime(user) {
if (user.lastLoginAt != null) {
const millisSinceEpoch = parseInt(user.lastLoginAt, 10);
const authTime = new Date(millisSinceEpoch);
if (isNaN(authTime.getTime())) {
throw new Error(`Internal assertion error: invalid user.lastLoginAt = ${user.lastLoginAt}`);
}
return authTime;
}
else if (user.lastRefreshAt != null) {
const authTime = new Date(user.lastRefreshAt);
if (isNaN(authTime.getTime())) {
throw new Error(`Internal assertion error: invalid user.lastRefreshAt = ${user.lastRefreshAt}`);
}
return authTime;
}
else {
throw new Error(`Internal assertion error: Missing user.lastLoginAt and user.lastRefreshAt`);
}
}
function verifyPhoneNumber(state, sessionInfo, code) {
const verification = state.getVerificationCodeBySessionInfo(sessionInfo);
(0, errors_1.assert)(verification, "INVALID_SESSION_INFO");
(0, errors_1.assert)(verification.code === code, "INVALID_CODE");
state.deleteVerificationCodeBySessionInfo(sessionInfo);
return verification.phoneNumber;
}
const CUSTOM_ATTRIBUTES_MAX_LENGTH = 1000;
function validateSerializedCustomClaims(claims) {
(0, errors_1.assert)(claims.length <= CUSTOM_ATTRIBUTES_MAX_LENGTH, "CLAIMS_TOO_LARGE");
let parsed;
try {
parsed = JSON.parse(claims);
}
catch (_a) {
throw new errors_1.BadRequestError("INVALID_CLAIMS");
}
validateCustomClaims(parsed);
}
const FORBIDDEN_CUSTOM_CLAIMS = [
"iss",
"aud",
"sub",
"iat",
"exp",
"nbf",
"jti",
"nonce",
"azp",
"acr",
"amr",
"cnf",
"auth_time",
"firebase",
"at_hash",
"c_hash",
];
function validateCustomClaims(claims) {
(0, errors_1.assert)(typeof claims === "object" && claims != null && !Array.isArray(claims), "INVALID_CLAIMS");
for (const reservedField of FORBIDDEN_CUSTOM_CLAIMS) {
(0, errors_1.assert)(!(reservedField in claims), `FORBIDDEN_CLAIM : ${reservedField}`);
}
}
function newRandomId(length, existingIds) {
for (let i = 0; i < 10; i++) {
const id = (0, utils_1.randomId)(length);
if (!(existingIds === null || existingIds === void 0 ? void 0 : existingIds.has(id))) {
return id;
}
}
throw new errors_1.InternalError("INTERNAL_ERROR : Failed to generate a random ID after 10 attempts", "INTERNAL");
}
function getMfaEnrollmentsFromRequest(state, request, options) {
const enrollments = [];
const phoneNumbers = new Set();
const enrollmentIds = new Set();
for (const enrollment of request) {
(0, errors_1.assert)(enrollment.phoneInfo && (0, utils_1.isValidPhoneNumber)(enrollment.phoneInfo), "INVALID_MFA_PHONE_NUMBER : Invalid format.");
if (!phoneNumbers.has(enrollment.phoneInfo)) {
const mfaEnrollmentId = (options === null || options === void 0 ? void 0 : options.generateEnrollmentIds)
? newRandomId(28, enrollmentIds)
: enrollment.mfaEnrollmentId;
(0, errors_1.assert)(mfaEnrollmentId, "INVALID_MFA_ENROLLMENT_ID : mfaEnrollmentId must be defined.");
(0, errors_1.assert)(!enrollmentIds.has(mfaEnrollmentId), "DUPLICATE_MFA_ENROLLMENT_ID");
enrollments.push(Object.assign(Object.assign({}, enrollment), { mfaEnrollmentId, unobfuscatedPhoneInfo: enrollment.phoneInfo }));
phoneNumbers.add(enrollment.phoneInfo);
enrollmentIds.add(mfaEnrollmentId);
}
}
return state.validateMfaEnrollments(enrollments);
}
function getNormalizedUri(reqBody) {
(0, errors_1.assert)(reqBody.requestUri, "MISSING_REQUEST_URI");
const normalizedUri = (0, utils_1.parseAbsoluteUri)(reqBody.requestUri);
(0, errors_1.assert)(normalizedUri, "INVALID_REQUEST_URI");
if (reqBody.postBody) {
const postBodyParams = new url_1.URLSearchParams(reqBody.postBody);
for (const key of postBodyParams.keys()) {
normalizedUri.searchParams.set(key, postBodyParams.get(key));
}
}
const fragment = normalizedUri.hash.replace(/^#/, "");
if (fragment) {
const fragmentParams = new url_1.URLSearchParams(fragment);
for (const key of fragmentParams.keys()) {
normalizedUri.searchParams.set(key, fragmentParams.get(key));
}
normalizedUri.hash = "";
}
return normalizedUri;
}
function parseClaims(idTokenOrJsonClaims) {
if (!idTokenOrJsonClaims) {
return undefined;
}
let claims;
if (idTokenOrJsonClaims.startsWith("{")) {
try {
claims = JSON.parse(idTokenOrJsonClaims);
}
catch (_a) {
throw new errors_1.BadRequestError(`INVALID_IDP_RESPONSE : Unable to parse id_token: ${idTokenOrJsonClaims} ((Auth Emulator failed to parse fake id_token as strict JSON.))`);
}
}
else {
const decoded = (0, jsonwebtoken_1.decode)(idTokenOrJsonClaims, { json: true });
if (!decoded) {
return undefined;
}
claims = decoded;
}
(0, errors_1.assert)(claims.sub, 'INVALID_IDP_RESPONSE : Invalid Idp Response: id_token missing required fields. ((Missing "sub" field. This field is required and must be a unique identifier.))');
(0, errors_1.assert)(typeof claims.sub === "string", 'INVALID_IDP_RESPONSE : ((The "sub" field must be a string.))');
return claims;
}
function fakeFetchUserInfoFromIdp(providerId, claims, samlResponse) {
var _a, _b, _c, _d, _e;
const rawId = claims.sub;
const email = claims.email ? (0, utils_1.canonicalizeEmailAddress)(claims.email) : undefined;
const emailVerified = !!claims.email_verified;
const displayName = claims.name;
const photoUrl = claims.picture;
const response = {
kind: "identitytoolkit#VerifyAssertionResponse",
context: "",
providerId,
displayName,
fullName: displayName,
screenName: claims.screen_name,
email,
emailVerified,
photoUrl,
};
let federatedId = rawId;
switch (providerId) {
case "google.com": {
federatedId = `https://accounts.google.com/${rawId}`;
let grantedScopes = "openid https://www.googleapis.com/auth/userinfo.profile";
if (email) {
grantedScopes += " https://www.googleapis.com/auth/userinfo.email";
}
response.firstName = claims.given_name;
response.lastName = claims.family_name;
response.rawUserInfo = JSON.stringify({
granted_scopes: grantedScopes,
id: rawId,
name: displayName,
given_name: claims.given_name,
family_name: claims.family_name,
verified_email: emailVerified,
locale: "en",
email,
picture: photoUrl,
});
break;
}
case (_a = providerId.match(/^saml\./)) === null || _a === void 0 ? void 0 : _a.input:
const nameId = (_c = (_b = samlResponse === null || samlResponse === void 0 ? void 0 : samlResponse.assertion) === null || _b === void 0 ? void 0 : _b.subject) === null || _c === void 0 ? void 0 : _c.nameId;
response.email = nameId && (0, utils_1.isValidEmailAddress)(nameId) ? nameId : response.email;
response.emailVerified = true;
response.rawUserInfo = JSON.stringify((_d = samlResponse === null || samlResponse === void 0 ? void 0 : samlResponse.assertion) === null || _d === void 0 ? void 0 : _d.attributeStatements);
break;
case (_e = providerId.match(/^oidc\./)) === null || _e === void 0 ? void 0 : _e.input:
default:
response.rawUserInfo = JSON.stringify(claims);
break;
}
response.federatedId = federatedId;
return { response, rawId };
}
function handleLinkIdp(state, response, userFromIdToken) {
if (state.oneAccountPerEmail && response.email) {
const userMatchingEmail = state.getUserByEmail(response.email);
(0, errors_1.assert)(!userMatchingEmail || userMatchingEmail.localId === userFromIdToken.localId, "EMAIL_EXISTS");
}
response.localId = userFromIdToken.localId;
const fields = {};
if (state.oneAccountPerEmail && response.email && !userFromIdToken.email) {
fields.email = response.email;
fields.emailVerified = response.emailVerified;
}
if (response.email &&
response.emailVerified &&
(fields.email || userFromIdToken.email) === response.email) {
fields.emailVerified = true;
}
return { accountUpdates: { fields }, response };
}
function handleIdpSigninEmailNotRequired(response, userMatchingProvider) {
if (userMatchingProvider) {
return {
response: Object.assign(Object.assign({}, response), { localId: userMatchingProvider.localId }),
accountUpdates: {},
};
}
else {
return handleIdpSignUp(response, { emailRequired: false });
}
}
function handleIdpSigninEmailRequired(response, rawId, userMatchingProvider, userMatchingEmail) {
var _a, _b, _c;
if (userMatchingProvider) {
return {
response: Object.assign(Object.assign({}, response), { localId: userMatchingProvider.localId }),
accountUpdates: {},
};
}
else if (userMatchingEmail) {
if (response.emailVerified) {
if ((_a = userMatchingEmail.providerUserInfo) === null || _a === void 0 ? void 0 : _a.some((info) => info.providerId === response.providerId && info.rawId !== rawId)) {
response.emailRecycled = true;
}
response.localId = userMatchingEmail.localId;
const accountUpdates = {
fields: {},
};
if (!userMatchingEmail.emailVerified) {
accountUpdates.fields.passwordHash = undefined;
accountUpdates.fields.phoneNumber = undefined;
accountUpdates.fields.validSince = (0, utils_1.toUnixTimestamp)(new Date()).toString();
accountUpdates.deleteProviders = (_b = userMatchingEmail.providerUserInfo) === null || _b === void 0 ? void 0 : _b.map((info) => info.providerId);
}
accountUpdates.fields.dateOfBirth = response.dateOfBirth;
accountUpdates.fields.displayName = response.displayName;
accountUpdates.fields.language = response.language;
accountUpdates.fields.photoUrl = response.photoUrl;
accountUpdates.fields.screenName = response.screenName;
accountUpdates.fields.emailVerified = true;
return { response, accountUpdates };
}
else {
response.needConfirmation = true;
response.localId = userMatchingEmail.localId;
response.verifiedProvider = (_c = userMatchingEmail.providerUserInfo) === null || _c === void 0 ? void 0 : _c.map((info) => info.providerId).filter((id) => id !== state_1.PROVIDER_PASSWORD && id !== state_1.PROVIDER_PHONE);
return { response, accountUpdates: {} };
}
}
else {
return handleIdpSignUp(response, { emailRequired: true });
}
}
function handleIdpSignUp(response, options) {
const accountUpdates = {
fields: {
dateOfBirth: response.dateOfBirth,
displayName: response.displayName,
language: response.language,
photoUrl: response.photoUrl,
screenName: response.screenName,
},
};
if (options.emailRequired && response.email) {
accountUpdates.fields.email = response.email;
accountUpdates.fields.emailVerified = response.emailVerified;
}
return {
response: Object.assign(Object.assign({}, response), { isNewUser: true }),
accountUpdates,
};
}
function mfaPending(state, user, signInProvider) {
if (!user.mfaInfo) {
throw new Error("Internal assertion error: mfaPending called on user without MFA.");
}
const pendingCredentialPayload = {
_AuthEmulatorMfaPendingCredential: "DO NOT MODIFY",
localId: user.localId,
signInProvider,
projectId: state.projectId,
};
if (state instanceof state_1.TenantProjectState) {
pendingCredentialPayload.tenantId = state.tenantId;
}
const mfaPendingCredential = Buffer.from(JSON.stringify(pendingCredentialPayload), "utf8").toString("base64");
return { mfaPendingCredential, mfaInfo: user.mfaInfo.map(redactMfaInfo) };
}
function redactMfaInfo(mfaInfo) {
return {
displayName: mfaInfo.displayName,
enrolledAt: mfaInfo.enrolledAt,
mfaEnrollmentId: mfaInfo.mfaEnrollmentId,
phoneInfo: mfaInfo.unobfuscatedPhoneInfo
? obfuscatePhoneNumber(mfaInfo.unobfuscatedPhoneInfo)
: undefined,
};
}
function obfuscatePhoneNumber(phoneNumber) {
const split = phoneNumber.split("");
let digitsEncountered = 0;
for (let i = split.length - 1; i >= 0; i--) {
if (/[0-9]/.test(split[i])) {
digitsEncountered++;
if (digitsEncountered > 4) {
split[i] = "*";
}
}
}
return split.join("");
}
function parsePendingCredential(state, pendingCredential) {
let pendingCredentialPayload;
try {
const json = Buffer.from(pendingCredential, "base64").toString("utf8");
pendingCredentialPayload = JSON.parse(json);
}
catch (_a) {
(0, errors_1.assert)(false, "((Invalid phoneVerificationInfo.mfaPendingCredential.))");
}
(0, errors_1.assert)(pendingCredentialPayload._AuthEmulatorMfaPendingCredential, "((Invalid phoneVerificationInfo.mfaPendingCredential.))");
(0, errors_1.assert)(pendingCredentialPayload.projectId === state.projectId, "INVALID_PROJECT_ID : Project ID does not match MFA pending credential.");
if (state instanceof state_1.TenantProjectState) {
(0, errors_1.assert)(pendingCredentialPayload.tenantId === state.tenantId, "INVALID_PROJECT_ID : Project ID does not match MFA pending credential.");
}
const { localId, signInProvider } = pendingCredentialPayload;
const user = state.getUserByLocalId(localId);
(0, errors_1.assert)(user, "((User in pendingCredentialPayload does not exist.))");
return { user, signInProvider };
}
function createTenant(state, reqBody) {
var _a, _b, _c, _d, _e;
if (!(state instanceof state_1.AgentProjectState)) {
throw new errors_1.InternalError("INTERNAL_ERROR : Can only create tenant in agent project", "INTERNAL");
}
const mfaConfig = (_a = reqBody.mfaConfig) !== null && _a !== void 0 ? _a : {};
if (!("state" in mfaConfig)) {
mfaConfig.state = "DISABLED";
}
if (!("enabledProviders" in mfaConfig)) {
mfaConfig.enabledProviders = [];
}
const tenant = {
displayName: reqBody.displayName,
allowPasswordSignup: (_b = reqBody.allowPasswordSignup) !== null && _b !== void 0 ? _b : false,
enableEmailLinkSignin: (_c = reqBody.enableEmailLinkSignin) !== null && _c !== void 0 ? _c : false,
enableAnonymousUser: (_d = reqBody.enableAnonymousUser) !== null && _d !== void 0 ? _d : false,
disableAuth: (_e = reqBody.disableAuth) !== null && _e !== void 0 ? _e : false,
mfaConfig: mfaConfig,
tenantId: "",
};
return state.createTenant(tenant);
}
function listTenants(state, reqBody, ctx) {
(0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Can only list tenants in agent project.))");
const pageSize = Math.min(Math.floor(ctx.params.query.pageSize) || 20, 1000);
const tenants = state.listTenants(ctx.params.query.pageToken);
let nextPageToken = undefined;
if (pageSize > 0 && tenants.length >= pageSize) {
tenants.length = pageSize;
nextPageToken = tenants[tenants.length - 1].tenantId;
}
return {
nextPageToken,
tenants,
};
}
function deleteTenant(state) {
(0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Can only delete tenant on tenant projects.))");
state.delete();
return {};
}
function getTenant(state) {
(0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Can only get tenant on tenant projects.))");
return state.tenantConfig;
}
function updateTenant(state, reqBody, ctx) {
(0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Can only update tenant on tenant projects.))");
return state.updateTenant(reqBody, ctx.params.query.updateMask);
}
function isMfaEnabled(state, user) {
var _a;
return ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") &&
((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length));
}
async function fetchBlockingFunction(state, event, user, options = {}, oauthTokens = {}, timeoutMs = 60000) {
const url = state.getBlockingFunctionUri(event);
if (!url) {
return { updates: {} };
}
const jwt = generateBlockingFunctionJwt(state, event, url, timeoutMs, user, options, oauthTokens);
const reqBody = {
data: {
jwt,
},
};
const controller = new abort_controller_1.default();
const timeout = setTimeout(() => {
controller.abort();
}, timeoutMs);
let response;
let ok;
let status;
let text;
try {
const res = await (0, node_fetch_1.default)(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(reqBody),
signal: controller.signal,
});
ok = res.ok;
status = res.status;
text = await res.text();
}
catch (thrown) {
const err = thrown instanceof Error ? thrown : new Error(thrown);
const isAbortError = err.name.includes("AbortError");
if (isAbortError) {
throw new errors_1.InternalError(`BLOCKING_FUNCTION_ERROR_RESPONSE : ((Deadline exceeded making request to ${url}.))`, err.message);
}
throw new errors_1.InternalError(`BLOCKING_FUNCTION_ERROR_RESPONSE : ((Failed to make request to ${url}.))`, err.message);
}
finally {
clearTimeout(timeout);
}
(0, errors_1.assert)(ok, `BLOCKING_FUNCTION_ERROR_RESPONSE : ((HTTP request to ${url} returned HTTP error ${status}: ${text}))`);
try {
response = JSON.parse(text);
}
catch (thrown) {
const err = thrown instanceof Error ? thrown : new Error(thrown);
throw new errors_1.InternalError(`BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response body is not valid JSON.))`, err.message);
}
return processBlockingFunctionResponse(event, response);
}
function processBlockingFunctionResponse(event, response) {
let extraClaims;
const updates = {};
if (response.userRecord) {
const userRecord = response.userRecord;
(0, errors_1.assert)(userRecord.updateMask, "BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response UserRecord is missing updateMask.))");
const mask = userRecord.updateMask;
const fields = mask.split(",");
for (const field of fields) {
switch (field) {
case "displayName":
case "photoUrl":
updates[field] = coercePrimitiveToString(userRecord[field]);
break;
case "disabled":
case "emailVerified":
updates[field] = !!userRecord[field];
break;
case "customClaims":
const customClaims = JSON.stringify(userRecord.customClaims);
validateSerializedCustomClaims(customClaims);
updates.customAttributes = customClaims;
break;
case "sessionClaims":
if (event !== state_1.BlockingFunctionEvents.BEFORE_SIGN_IN) {
break;
}
try {
extraClaims = userRecord.sessionClaims;
}
catch (_a) {
throw new errors_1.BadRequestError("BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response has malformed session claims.))");
}
break;
default:
break;
}
}
}
return { updates, extraClaims };
}
function generateBlockingFunctionJwt(state, event, url, timeoutMs, user, options, oauthTokens) {
const issuedAt = (0, utils_1.toUnixTimestamp)(new Date());
const jwt = {
iss: `https://securetoken.google.com/${state.projectId}`,
aud: url,
iat: issuedAt,
exp: issuedAt + timeoutMs / 100,
event_id: (0, utils_1.randomBase64UrlStr)(16),
event_type: event,
user_agent: "NotYetSupportedInFirebaseAuthEmulator",
ip_address: "127.0.0.1",
locale: "en",
user_record: {
uid: user.localId,
email: user.email,
email_verified: user.emailVerified,
display_name: user.displayName,
photo_url: user.photoUrl,
disabled: user.disabled,
phone_number: user.phoneNumber,
custom_claims: JSON.parse(user.customAttributes || "{}"),
},
sub: user.localId,
sign_in_method: options.signInMethod,
sign_in_second_factor: options.signInSecondFactor,
sign_in_attributes: options.signInAttributes,
raw_user_info: options.rawUserInfo,
};
if (state instanceof state_1.TenantProjectState) {
jwt.tenant_id = state.tenantId;
jwt.user_record.tenant_id = state.tenantId;
}
const providerData = [];
if (user.providerUserInfo) {
for (const providerUserInfo of user.providerUserInfo) {
const provider = {
provider_id: providerUserInfo.providerId,
display_name: providerUserInfo.displayName,
photo_url: providerUserInfo.photoUrl,
email: providerUserInfo.email,
uid: providerUserInfo.rawId,
phone_number: providerUserInfo.phoneNumber,
};
providerData.push(provider);
}
}
jwt.user_record.provider_data = providerData;
if (user.mfaInfo) {
const enrolledFactors = [];
for (const mfaEnrollment of user.mfaInfo) {
if (!mfaEnrollment.mfaEnrollmentId) {
continue;
}
const enrolledFactor = {
uid: mfaEnrollment.mfaEnrollmentId,
display_name: mfaEnrollment.displayName,
enrollment_time: mfaEnrollment.enrolledAt,
phone_number: mfaEnrollment.phoneInfo,
factor_id: state_1.PROVIDER_PHONE,
};
enrolledFactors.push(enrolledFactor);
}
jwt.user_record.multi_factor = {
enrolled_factors: enrolledFactors,
};
}
if (user.lastLoginAt || user.createdAt) {
jwt.user_record.metadata = {
last_sign_in_time: user.lastLoginAt,
creation_time: user.createdAt,
};
}
if (state.shouldForwardCredentialToBlockingFunction("accessToken")) {
jwt.oauth_access_token = oauthTokens.oauthAccessToken;
jwt.oauth_token_secret = oauthTokens.oauthTokenSecret;
jwt.oauth_expires_in = oauthTokens.oauthExpiresIn;
}
if (state.shouldForwardCredentialToBlockingFunction("idToken")) {
jwt.oauth_id_token = oauthTokens.oauthIdToken;
}
if (state.shouldForwardCredentialToBlockingFunction("refreshToken")) {
jwt.oauth_refresh_token = oauthTokens.oauthRefreshToken;
}
const jwtStr = (0, jsonwebtoken_1.sign)(jwt, "fake-secret", {
algorithm: "none",
});
return jwtStr;
}
function parseBlockingFunctionJwt(jwt) {
const decoded = (0, jsonwebtoken_1.decode)(jwt, { json: true });
(0, errors_1.assert)(decoded, "((Invalid blocking function jwt.))");
(0, errors_1.assert)(decoded.iss, "((Invalid blocking function jwt, missing `iss` claim.))");
(0, errors_1.assert)(decoded.aud, "((Invalid blocking function jwt, missing `aud` claim.))");
(0, errors_1.assert)(decoded.user_record, "((Invalid blocking function jwt, missing `user_record` claim.))");
return decoded;
}
exports.parseBlockingFunctionJwt = parseBlockingFunctionJwt;