No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

operations.js 98KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.parseBlockingFunctionJwt = exports.setAccountInfoImpl = exports.resetPassword = exports.SESSION_COOKIE_MAX_VALID_DURATION = exports.CUSTOM_TOKEN_AUDIENCE = exports.authOperations = void 0;
  4. const url_1 = require("url");
  5. const jsonwebtoken_1 = require("jsonwebtoken");
  6. const node_fetch_1 = require("node-fetch");
  7. const abort_controller_1 = require("abort-controller");
  8. const utils_1 = require("./utils");
  9. const errors_1 = require("./errors");
  10. const types_1 = require("../types");
  11. const emulatorLogger_1 = require("../emulatorLogger");
  12. const state_1 = require("./state");
  13. exports.authOperations = {
  14. identitytoolkit: {
  15. getProjects,
  16. getRecaptchaParams,
  17. accounts: {
  18. createAuthUri,
  19. delete: deleteAccount,
  20. lookup,
  21. resetPassword,
  22. sendOobCode,
  23. sendVerificationCode,
  24. signInWithCustomToken,
  25. signInWithEmailLink,
  26. signInWithIdp,
  27. signInWithPassword,
  28. signInWithPhoneNumber,
  29. signUp,
  30. update: setAccountInfo,
  31. mfaEnrollment: {
  32. finalize: mfaEnrollmentFinalize,
  33. start: mfaEnrollmentStart,
  34. withdraw: mfaEnrollmentWithdraw,
  35. },
  36. mfaSignIn: {
  37. start: mfaSignInStart,
  38. finalize: mfaSignInFinalize,
  39. },
  40. },
  41. projects: {
  42. createSessionCookie,
  43. queryAccounts,
  44. getConfig,
  45. updateConfig,
  46. accounts: {
  47. _: signUp,
  48. delete: deleteAccount,
  49. lookup,
  50. query: queryAccounts,
  51. sendOobCode,
  52. update: setAccountInfo,
  53. batchCreate,
  54. batchDelete,
  55. batchGet,
  56. },
  57. tenants: {
  58. create: createTenant,
  59. delete: deleteTenant,
  60. get: getTenant,
  61. list: listTenants,
  62. patch: updateTenant,
  63. createSessionCookie,
  64. accounts: {
  65. _: signUp,
  66. batchCreate,
  67. batchDelete,
  68. batchGet,
  69. delete: deleteAccount,
  70. lookup,
  71. query: queryAccounts,
  72. sendOobCode,
  73. update: setAccountInfo,
  74. },
  75. },
  76. },
  77. },
  78. securetoken: {
  79. token: grantToken,
  80. },
  81. emulator: {
  82. projects: {
  83. accounts: {
  84. delete: deleteAllAccountsInProject,
  85. },
  86. config: {
  87. get: getEmulatorProjectConfig,
  88. update: updateEmulatorProjectConfig,
  89. },
  90. oobCodes: {
  91. list: listOobCodesInProject,
  92. },
  93. verificationCodes: {
  94. list: listVerificationCodesInProject,
  95. },
  96. },
  97. },
  98. };
  99. const PASSWORD_MIN_LENGTH = 6;
  100. exports.CUSTOM_TOKEN_AUDIENCE = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit";
  101. const MFA_INELIGIBLE_PROVIDER = new Set([
  102. state_1.PROVIDER_ANONYMOUS,
  103. state_1.PROVIDER_PHONE,
  104. state_1.PROVIDER_CUSTOM,
  105. state_1.PROVIDER_GAME_CENTER,
  106. ]);
  107. async function signUp(state, reqBody, ctx) {
  108. var _a, _b, _c, _d;
  109. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  110. let provider;
  111. const timestamp = new Date();
  112. let updates = {
  113. lastLoginAt: timestamp.getTime().toString(),
  114. };
  115. if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
  116. if (reqBody.idToken) {
  117. (0, errors_1.assert)(!reqBody.localId, "UNEXPECTED_PARAMETER : User ID");
  118. }
  119. if (reqBody.localId) {
  120. (0, errors_1.assert)(!state.getUserByLocalId(reqBody.localId), "DUPLICATE_LOCAL_ID");
  121. }
  122. updates.displayName = reqBody.displayName;
  123. updates.photoUrl = reqBody.photoUrl;
  124. updates.emailVerified = reqBody.emailVerified || false;
  125. if (reqBody.phoneNumber) {
  126. (0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
  127. (0, errors_1.assert)(!state.getUserByPhoneNumber(reqBody.phoneNumber), "PHONE_NUMBER_EXISTS");
  128. updates.phoneNumber = reqBody.phoneNumber;
  129. }
  130. if (reqBody.disabled) {
  131. updates.disabled = true;
  132. }
  133. }
  134. else {
  135. (0, errors_1.assert)(!reqBody.localId, "UNEXPECTED_PARAMETER : User ID");
  136. if (reqBody.idToken || reqBody.password || reqBody.email) {
  137. updates.displayName = reqBody.displayName;
  138. updates.emailVerified = false;
  139. (0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
  140. (0, errors_1.assert)(reqBody.password, "MISSING_PASSWORD");
  141. provider = state_1.PROVIDER_PASSWORD;
  142. (0, errors_1.assert)(state.allowPasswordSignup, "OPERATION_NOT_ALLOWED");
  143. }
  144. else {
  145. provider = state_1.PROVIDER_ANONYMOUS;
  146. (0, errors_1.assert)(state.enableAnonymousUser, "ADMIN_ONLY_OPERATION");
  147. }
  148. }
  149. if (reqBody.email) {
  150. (0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
  151. const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
  152. (0, errors_1.assert)(!state.getUserByEmail(email), "EMAIL_EXISTS");
  153. updates.email = email;
  154. }
  155. if (reqBody.password) {
  156. (0, errors_1.assert)(reqBody.password.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters`);
  157. updates.salt = "fakeSalt" + (0, utils_1.randomId)(20);
  158. updates.passwordHash = hashPassword(reqBody.password, updates.salt);
  159. updates.passwordUpdatedAt = Date.now();
  160. updates.validSince = (0, utils_1.toUnixTimestamp)(new Date()).toString();
  161. }
  162. if (reqBody.mfaInfo) {
  163. updates.mfaInfo = getMfaEnrollmentsFromRequest(state, reqBody.mfaInfo, {
  164. generateEnrollmentIds: true,
  165. });
  166. }
  167. if (state instanceof state_1.TenantProjectState) {
  168. updates.tenantId = state.tenantId;
  169. }
  170. let user;
  171. if (reqBody.idToken) {
  172. ({ user } = parseIdToken(state, reqBody.idToken));
  173. }
  174. let extraClaims;
  175. if (!user) {
  176. updates.createdAt = timestamp.getTime().toString();
  177. const localId = (_b = reqBody.localId) !== null && _b !== void 0 ? _b : state.generateLocalId();
  178. if (reqBody.email && !((_c = ctx.security) === null || _c === void 0 ? void 0 : _c.Oauth2)) {
  179. const userBeforeCreate = Object.assign({ localId }, updates);
  180. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, { signInMethod: "password" });
  181. updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
  182. }
  183. user = state.createUserWithLocalId(localId, updates);
  184. (0, errors_1.assert)(user, "DUPLICATE_LOCAL_ID");
  185. if (reqBody.email && !((_d = ctx.security) === null || _d === void 0 ? void 0 : _d.Oauth2)) {
  186. if (!user.disabled) {
  187. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "password" });
  188. updates = blockingResponse.updates;
  189. extraClaims = blockingResponse.extraClaims;
  190. user = state.updateUserByLocalId(user.localId, updates);
  191. }
  192. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  193. }
  194. }
  195. else {
  196. user = state.updateUserByLocalId(user.localId, updates);
  197. }
  198. return Object.assign({ kind: "identitytoolkit#SignupNewUserResponse", localId: user.localId, displayName: user.displayName, email: user.email }, (provider ? issueTokens(state, user, provider, { extraClaims }) : {}));
  199. }
  200. function lookup(state, reqBody, ctx) {
  201. var _a, _b, _c, _d, _e;
  202. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  203. const seenLocalIds = new Set();
  204. const users = [];
  205. function tryAddUser(maybeUser) {
  206. if (maybeUser && !seenLocalIds.has(maybeUser.localId)) {
  207. users.push(maybeUser);
  208. seenLocalIds.add(maybeUser.localId);
  209. }
  210. }
  211. if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
  212. if (reqBody.initialEmail) {
  213. throw new errors_1.NotImplementedError("Lookup by initialEmail is not implemented.");
  214. }
  215. for (const localId of (_b = reqBody.localId) !== null && _b !== void 0 ? _b : []) {
  216. tryAddUser(state.getUserByLocalId(localId));
  217. }
  218. for (const email of (_c = reqBody.email) !== null && _c !== void 0 ? _c : []) {
  219. tryAddUser(state.getUserByEmail(email));
  220. }
  221. for (const phoneNumber of (_d = reqBody.phoneNumber) !== null && _d !== void 0 ? _d : []) {
  222. tryAddUser(state.getUserByPhoneNumber(phoneNumber));
  223. }
  224. for (const { providerId, rawId } of (_e = reqBody.federatedUserId) !== null && _e !== void 0 ? _e : []) {
  225. if (!providerId || !rawId) {
  226. continue;
  227. }
  228. tryAddUser(state.getUserByProviderRawId(providerId, rawId));
  229. }
  230. }
  231. else {
  232. (0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
  233. const { user } = parseIdToken(state, reqBody.idToken);
  234. users.push(redactPasswordHash(user));
  235. }
  236. return {
  237. kind: "identitytoolkit#GetAccountInfoResponse",
  238. users: users.length ? users : undefined,
  239. };
  240. }
  241. function batchCreate(state, reqBody) {
  242. var _a, _b;
  243. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  244. (0, errors_1.assert)((_a = reqBody.users) === null || _a === void 0 ? void 0 : _a.length, "MISSING_USER_ACCOUNT");
  245. if (reqBody.sanityCheck) {
  246. if (state.oneAccountPerEmail) {
  247. const existingEmails = new Set();
  248. for (const userInfo of reqBody.users) {
  249. if (userInfo.email) {
  250. (0, errors_1.assert)(!existingEmails.has(userInfo.email), `DUPLICATE_EMAIL : ${userInfo.email}`);
  251. existingEmails.add(userInfo.email);
  252. }
  253. }
  254. }
  255. const existingProviderAccounts = new Set();
  256. for (const userInfo of reqBody.users) {
  257. for (const { providerId, rawId } of (_b = userInfo.providerUserInfo) !== null && _b !== void 0 ? _b : []) {
  258. const key = `${providerId}:${rawId}`;
  259. (0, errors_1.assert)(!existingProviderAccounts.has(key), `DUPLICATE_RAW_ID : Provider id(${providerId}), Raw id(${rawId})`);
  260. existingProviderAccounts.add(key);
  261. }
  262. }
  263. }
  264. if (!reqBody.allowOverwrite) {
  265. const existingLocalIds = new Set();
  266. for (const userInfo of reqBody.users) {
  267. const localId = userInfo.localId || "";
  268. (0, errors_1.assert)(!existingLocalIds.has(localId), `DUPLICATE_LOCAL_ID : ${localId}`);
  269. existingLocalIds.add(localId);
  270. }
  271. }
  272. const errors = [];
  273. for (let index = 0; index < reqBody.users.length; index++) {
  274. const userInfo = reqBody.users[index];
  275. try {
  276. (0, errors_1.assert)(userInfo.localId, "localId is missing");
  277. const uploadTime = new Date();
  278. const fields = {
  279. displayName: userInfo.displayName,
  280. photoUrl: userInfo.photoUrl,
  281. lastLoginAt: userInfo.lastLoginAt,
  282. };
  283. if (userInfo.tenantId) {
  284. (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.");
  285. }
  286. if (state instanceof state_1.TenantProjectState) {
  287. fields.tenantId = state.tenantId;
  288. }
  289. if (userInfo.passwordHash) {
  290. fields.passwordHash = userInfo.passwordHash;
  291. fields.salt = userInfo.salt;
  292. fields.passwordUpdatedAt = uploadTime.getTime();
  293. }
  294. else if (userInfo.rawPassword) {
  295. fields.salt = userInfo.salt || "fakeSalt" + (0, utils_1.randomId)(20);
  296. fields.passwordHash = hashPassword(userInfo.rawPassword, fields.salt);
  297. fields.passwordUpdatedAt = uploadTime.getTime();
  298. }
  299. if (userInfo.customAttributes) {
  300. validateSerializedCustomClaims(userInfo.customAttributes);
  301. fields.customAttributes = userInfo.customAttributes;
  302. }
  303. if (userInfo.providerUserInfo) {
  304. fields.providerUserInfo = [];
  305. for (const providerUserInfo of userInfo.providerUserInfo) {
  306. const { providerId, rawId, federatedId } = providerUserInfo;
  307. if (providerId === state_1.PROVIDER_PASSWORD || providerId === state_1.PROVIDER_PHONE) {
  308. continue;
  309. }
  310. if (!rawId || !providerId) {
  311. if (!federatedId) {
  312. (0, errors_1.assert)(false, "federatedId or (providerId & rawId) is required");
  313. }
  314. else {
  315. (0, errors_1.assert)(false, "((Parsing federatedId is not implemented in Auth Emulator; please specify providerId AND rawId as a workaround.))");
  316. }
  317. }
  318. const existingUserWithRawId = state.getUserByProviderRawId(providerId, rawId);
  319. (0, errors_1.assert)(!existingUserWithRawId || existingUserWithRawId.localId === userInfo.localId, "raw id exists in other account in database");
  320. fields.providerUserInfo.push(Object.assign(Object.assign({}, providerUserInfo), { providerId, rawId }));
  321. }
  322. }
  323. if (userInfo.phoneNumber) {
  324. (0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(userInfo.phoneNumber), "phone number format is invalid");
  325. fields.phoneNumber = userInfo.phoneNumber;
  326. }
  327. fields.validSince = (0, utils_1.toUnixTimestamp)(uploadTime).toString();
  328. fields.createdAt = uploadTime.getTime().toString();
  329. if (fields.createdAt && !isNaN(Number(userInfo.createdAt))) {
  330. fields.createdAt = userInfo.createdAt;
  331. }
  332. if (userInfo.email) {
  333. const email = userInfo.email;
  334. (0, errors_1.assert)((0, utils_1.isValidEmailAddress)(email), "email is invalid");
  335. const existingUserWithEmail = state.getUserByEmail(email);
  336. (0, errors_1.assert)(!existingUserWithEmail || existingUserWithEmail.localId === userInfo.localId, reqBody.sanityCheck && state.oneAccountPerEmail
  337. ? "email exists in other account in database"
  338. : `((Auth Emulator does not support importing duplicate email: ${email}))`);
  339. fields.email = (0, utils_1.canonicalizeEmailAddress)(email);
  340. }
  341. fields.emailVerified = !!userInfo.emailVerified;
  342. fields.disabled = !!userInfo.disabled;
  343. if (userInfo.mfaInfo && userInfo.mfaInfo.length > 0) {
  344. fields.mfaInfo = [];
  345. (0, errors_1.assert)(fields.email, "Second factor account requires email to be presented.");
  346. (0, errors_1.assert)(fields.emailVerified, "Second factor account requires email to be verified.");
  347. const existingIds = new Set();
  348. for (const enrollment of userInfo.mfaInfo) {
  349. if (enrollment.mfaEnrollmentId) {
  350. (0, errors_1.assert)(!existingIds.has(enrollment.mfaEnrollmentId), "Enrollment id already exists.");
  351. existingIds.add(enrollment.mfaEnrollmentId);
  352. }
  353. }
  354. for (const enrollment of userInfo.mfaInfo) {
  355. enrollment.mfaEnrollmentId = enrollment.mfaEnrollmentId || newRandomId(28, existingIds);
  356. enrollment.enrolledAt = enrollment.enrolledAt || new Date().toISOString();
  357. (0, errors_1.assert)(enrollment.phoneInfo, "Second factor not supported.");
  358. (0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(enrollment.phoneInfo), "Phone number format is invalid");
  359. enrollment.unobfuscatedPhoneInfo = enrollment.phoneInfo;
  360. fields.mfaInfo.push(enrollment);
  361. }
  362. }
  363. if (state.getUserByLocalId(userInfo.localId)) {
  364. (0, errors_1.assert)(reqBody.allowOverwrite, "localId belongs to an existing account - can not overwrite.");
  365. }
  366. state.overwriteUserWithLocalId(userInfo.localId, fields);
  367. }
  368. catch (e) {
  369. if (e instanceof errors_1.BadRequestError) {
  370. let message = e.message;
  371. if (message === "INVALID_CLAIMS") {
  372. message = "Invalid custom claims provided.";
  373. }
  374. else if (message === "CLAIMS_TOO_LARGE") {
  375. message = "Custom claims provided are too large.";
  376. }
  377. else if (message.startsWith("FORBIDDEN_CLAIM")) {
  378. message = "Custom claims provided include a reserved claim.";
  379. }
  380. errors.push({
  381. index,
  382. message,
  383. });
  384. }
  385. else {
  386. throw e;
  387. }
  388. }
  389. }
  390. return {
  391. kind: "identitytoolkit#UploadAccountResponse",
  392. error: errors,
  393. };
  394. }
  395. function batchDelete(state, reqBody) {
  396. var _a;
  397. const errors = [];
  398. const localIds = (_a = reqBody.localIds) !== null && _a !== void 0 ? _a : [];
  399. (0, errors_1.assert)(localIds.length > 0 && localIds.length <= 1000, "LOCAL_ID_LIST_EXCEEDS_LIMIT");
  400. for (let index = 0; index < localIds.length; index++) {
  401. const localId = localIds[index];
  402. const user = state.getUserByLocalId(localId);
  403. if (!user) {
  404. continue;
  405. }
  406. else if (!user.disabled && !reqBody.force) {
  407. errors.push({
  408. index,
  409. localId,
  410. message: "NOT_DISABLED : Disable the account before batch deletion.",
  411. });
  412. }
  413. else {
  414. state.deleteUser(user);
  415. }
  416. }
  417. return { errors: errors.length ? errors : undefined };
  418. }
  419. function batchGet(state, reqBody, ctx) {
  420. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  421. const maxResults = Math.min(Math.floor(ctx.params.query.maxResults) || 20, 1000);
  422. const users = state.queryUsers({}, { sortByField: "localId", order: "ASC", startToken: ctx.params.query.nextPageToken });
  423. let newPageToken = undefined;
  424. if (maxResults >= 0 && users.length >= maxResults) {
  425. users.length = maxResults;
  426. if (users.length) {
  427. newPageToken = users[users.length - 1].localId;
  428. }
  429. }
  430. return {
  431. kind: "identitytoolkit#DownloadAccountResponse",
  432. users,
  433. nextPageToken: newPageToken,
  434. };
  435. }
  436. function createAuthUri(state, reqBody) {
  437. var _a;
  438. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  439. const sessionId = reqBody.sessionId || (0, utils_1.randomId)(27);
  440. if (reqBody.providerId) {
  441. throw new errors_1.NotImplementedError("Sign-in with IDP is not yet supported.");
  442. }
  443. (0, errors_1.assert)(reqBody.identifier, "MISSING_IDENTIFIER");
  444. (0, errors_1.assert)(reqBody.continueUri, "MISSING_CONTINUE_URI");
  445. (0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.identifier), "INVALID_IDENTIFIER");
  446. const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.identifier);
  447. (0, errors_1.assert)((0, utils_1.parseAbsoluteUri)(reqBody.continueUri), "INVALID_CONTINUE_URI");
  448. const allProviders = [];
  449. const signinMethods = [];
  450. let registered = false;
  451. const users = state.getUsersByEmailOrProviderEmail(email);
  452. if (state.oneAccountPerEmail) {
  453. if (users.length) {
  454. registered = true;
  455. (_a = users[0].providerUserInfo) === null || _a === void 0 ? void 0 : _a.forEach(({ providerId }) => {
  456. if (providerId === state_1.PROVIDER_PASSWORD) {
  457. allProviders.push(providerId);
  458. if (users[0].passwordHash) {
  459. signinMethods.push(state_1.PROVIDER_PASSWORD);
  460. }
  461. if (users[0].emailLinkSignin) {
  462. signinMethods.push(state_1.SIGNIN_METHOD_EMAIL_LINK);
  463. }
  464. }
  465. else if (providerId !== state_1.PROVIDER_PHONE) {
  466. allProviders.push(providerId);
  467. signinMethods.push(providerId);
  468. }
  469. });
  470. }
  471. }
  472. else {
  473. const user = users.find((u) => u.email);
  474. if (user) {
  475. registered = true;
  476. if (user.passwordHash || user.emailLinkSignin) {
  477. allProviders.push(state_1.PROVIDER_PASSWORD);
  478. if (users[0].passwordHash) {
  479. signinMethods.push(state_1.PROVIDER_PASSWORD);
  480. }
  481. if (users[0].emailLinkSignin) {
  482. signinMethods.push(state_1.SIGNIN_METHOD_EMAIL_LINK);
  483. }
  484. }
  485. }
  486. }
  487. return {
  488. kind: "identitytoolkit#CreateAuthUriResponse",
  489. registered,
  490. allProviders,
  491. sessionId,
  492. signinMethods,
  493. };
  494. }
  495. const SESSION_COOKIE_MIN_VALID_DURATION = 5 * 60;
  496. exports.SESSION_COOKIE_MAX_VALID_DURATION = 14 * 24 * 60 * 60;
  497. function createSessionCookie(state, reqBody) {
  498. (0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
  499. const validDuration = Number(reqBody.validDuration) || exports.SESSION_COOKIE_MAX_VALID_DURATION;
  500. (0, errors_1.assert)(validDuration >= SESSION_COOKIE_MIN_VALID_DURATION &&
  501. validDuration <= exports.SESSION_COOKIE_MAX_VALID_DURATION, "INVALID_DURATION");
  502. const { payload } = parseIdToken(state, reqBody.idToken);
  503. const issuedAt = (0, utils_1.toUnixTimestamp)(new Date());
  504. const expiresAt = issuedAt + validDuration;
  505. const sessionCookie = (0, jsonwebtoken_1.sign)(Object.assign(Object.assign({}, payload), { iat: issuedAt, exp: expiresAt, iss: `https://session.firebase.google.com/${payload.aud}` }), "", {
  506. algorithm: "none",
  507. });
  508. return { sessionCookie };
  509. }
  510. function deleteAccount(state, reqBody, ctx) {
  511. var _a;
  512. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  513. let user;
  514. if ((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2) {
  515. (0, errors_1.assert)(reqBody.localId, "MISSING_LOCAL_ID");
  516. const maybeUser = state.getUserByLocalId(reqBody.localId);
  517. (0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
  518. user = maybeUser;
  519. }
  520. else {
  521. (0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
  522. user = parseIdToken(state, reqBody.idToken).user;
  523. }
  524. state.deleteUser(user);
  525. return {
  526. kind: "identitytoolkit#DeleteAccountResponse",
  527. };
  528. }
  529. function getProjects(state) {
  530. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  531. (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
  532. return {
  533. projectId: state.projectNumber,
  534. authorizedDomains: [
  535. "localhost",
  536. ],
  537. };
  538. }
  539. function getRecaptchaParams(state) {
  540. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  541. return {
  542. kind: "identitytoolkit#GetRecaptchaParamResponse",
  543. recaptchaStoken: "This-is-a-fake-token__Dont-send-this-to-the-Recaptcha-service__The-Auth-Emulator-does-not-support-Recaptcha",
  544. recaptchaSiteKey: "Fake-key__Do-not-send-this-to-Recaptcha_",
  545. };
  546. }
  547. function queryAccounts(state, reqBody) {
  548. var _a;
  549. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  550. if ((_a = reqBody.expression) === null || _a === void 0 ? void 0 : _a.length) {
  551. throw new errors_1.NotImplementedError("expression is not implemented.");
  552. }
  553. if (reqBody.returnUserInfo === false) {
  554. return {
  555. recordsCount: state.getUserCount().toString(),
  556. };
  557. }
  558. if (reqBody.limit) {
  559. throw new errors_1.NotImplementedError("limit is not implemented.");
  560. }
  561. reqBody.offset = reqBody.offset || "0";
  562. if (reqBody.offset !== "0") {
  563. throw new errors_1.NotImplementedError("offset is not implemented.");
  564. }
  565. if (!reqBody.order || reqBody.order === "ORDER_UNSPECIFIED") {
  566. reqBody.order = "ASC";
  567. }
  568. if (!reqBody.sortBy || reqBody.sortBy === "SORT_BY_FIELD_UNSPECIFIED") {
  569. reqBody.sortBy = "USER_ID";
  570. }
  571. let sortByField;
  572. if (reqBody.sortBy === "USER_ID") {
  573. sortByField = "localId";
  574. }
  575. else {
  576. throw new errors_1.NotImplementedError("Only sorting by USER_ID is implemented.");
  577. }
  578. const users = state.queryUsers({}, { order: reqBody.order, sortByField });
  579. return {
  580. recordsCount: users.length.toString(),
  581. userInfo: users,
  582. };
  583. }
  584. function resetPassword(state, reqBody) {
  585. var _a;
  586. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  587. (0, errors_1.assert)(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
  588. (0, errors_1.assert)(reqBody.oobCode, "MISSING_OOB_CODE");
  589. const oob = state.validateOobCode(reqBody.oobCode);
  590. (0, errors_1.assert)(oob, "INVALID_OOB_CODE");
  591. if (reqBody.newPassword) {
  592. (0, errors_1.assert)(oob.requestType === "PASSWORD_RESET", "INVALID_OOB_CODE");
  593. (0, errors_1.assert)(reqBody.newPassword.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters`);
  594. state.deleteOobCode(reqBody.oobCode);
  595. let user = state.getUserByEmail(oob.email);
  596. (0, errors_1.assert)(user, "INVALID_OOB_CODE");
  597. const salt = "fakeSalt" + (0, utils_1.randomId)(20);
  598. const passwordHash = hashPassword(reqBody.newPassword, salt);
  599. user = state.updateUserByLocalId(user.localId, {
  600. emailVerified: true,
  601. passwordHash,
  602. salt,
  603. passwordUpdatedAt: Date.now(),
  604. validSince: (0, utils_1.toUnixTimestamp)(new Date()).toString(),
  605. }, { deleteProviders: (_a = user.providerUserInfo) === null || _a === void 0 ? void 0 : _a.map((info) => info.providerId) });
  606. }
  607. return {
  608. kind: "identitytoolkit#ResetPasswordResponse",
  609. requestType: oob.requestType,
  610. email: oob.requestType === "EMAIL_SIGNIN" ? undefined : oob.email,
  611. };
  612. }
  613. exports.resetPassword = resetPassword;
  614. function sendOobCode(state, reqBody, ctx) {
  615. var _a;
  616. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  617. (0, errors_1.assert)(reqBody.requestType && reqBody.requestType !== "OOB_REQ_TYPE_UNSPECIFIED", "MISSING_REQ_TYPE");
  618. if (reqBody.returnOobLink) {
  619. (0, errors_1.assert)((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2, "INSUFFICIENT_PERMISSION");
  620. }
  621. if (reqBody.continueUrl) {
  622. (0, errors_1.assert)((0, utils_1.parseAbsoluteUri)(reqBody.continueUrl), "INVALID_CONTINUE_URI : ((expected an absolute URI with valid scheme and host))");
  623. }
  624. let email;
  625. let mode;
  626. switch (reqBody.requestType) {
  627. case "EMAIL_SIGNIN":
  628. (0, errors_1.assert)(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
  629. mode = "signIn";
  630. (0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
  631. email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
  632. break;
  633. case "PASSWORD_RESET":
  634. mode = "resetPassword";
  635. (0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
  636. email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
  637. (0, errors_1.assert)(state.getUserByEmail(email), "EMAIL_NOT_FOUND");
  638. break;
  639. case "VERIFY_EMAIL":
  640. mode = "verifyEmail";
  641. if (reqBody.returnOobLink && !reqBody.idToken) {
  642. (0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
  643. email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
  644. const maybeUser = state.getUserByEmail(email);
  645. (0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
  646. }
  647. else {
  648. const user = parseIdToken(state, reqBody.idToken || "").user;
  649. (0, errors_1.assert)(user.email, "MISSING_EMAIL");
  650. email = user.email;
  651. }
  652. break;
  653. default:
  654. throw new errors_1.NotImplementedError(reqBody.requestType);
  655. }
  656. if (reqBody.canHandleCodeInApp) {
  657. emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("WARN", "canHandleCodeInApp is unsupported in Auth Emulator. All OOB operations will complete via web.");
  658. }
  659. const url = (0, utils_1.authEmulatorUrl)(ctx.req);
  660. const oobRecord = createOobRecord(state, email, url, {
  661. requestType: reqBody.requestType,
  662. mode,
  663. continueUrl: reqBody.continueUrl,
  664. });
  665. if (reqBody.returnOobLink) {
  666. return {
  667. kind: "identitytoolkit#GetOobConfirmationCodeResponse",
  668. email,
  669. oobCode: oobRecord.oobCode,
  670. oobLink: oobRecord.oobLink,
  671. };
  672. }
  673. else {
  674. logOobMessage(oobRecord);
  675. return {
  676. kind: "identitytoolkit#GetOobConfirmationCodeResponse",
  677. email,
  678. };
  679. }
  680. }
  681. function sendVerificationCode(state, reqBody) {
  682. var _a;
  683. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  684. (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
  685. (0, errors_1.assert)(reqBody.phoneNumber && (0, utils_1.isValidPhoneNumber)(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
  686. const user = state.getUserByPhoneNumber(reqBody.phoneNumber);
  687. (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.");
  688. const { sessionInfo, phoneNumber, code } = state.createVerificationCode(reqBody.phoneNumber);
  689. emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To verify the phone number ${phoneNumber}, use the code ${code}.`);
  690. return {
  691. sessionInfo,
  692. };
  693. }
  694. function setAccountInfo(state, reqBody, ctx) {
  695. var _a;
  696. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  697. const url = (0, utils_1.authEmulatorUrl)(ctx.req);
  698. return setAccountInfoImpl(state, reqBody, {
  699. privileged: !!((_a = ctx.security) === null || _a === void 0 ? void 0 : _a.Oauth2),
  700. emulatorUrl: url,
  701. });
  702. }
  703. function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl = undefined } = {}) {
  704. var _a, _b;
  705. const unimplementedFields = [
  706. "provider",
  707. "upgradeToFederatedLogin",
  708. "linkProviderUserInfo",
  709. ];
  710. for (const field of unimplementedFields) {
  711. if (field in reqBody) {
  712. throw new errors_1.NotImplementedError(`${field} is not implemented yet.`);
  713. }
  714. }
  715. if (!privileged) {
  716. (0, errors_1.assert)(reqBody.idToken || reqBody.oobCode, "INVALID_REQ_TYPE : Unsupported request parameters.");
  717. (0, errors_1.assert)(reqBody.customAttributes == null, "INSUFFICIENT_PERMISSION");
  718. }
  719. else {
  720. (0, errors_1.assert)(reqBody.localId, "MISSING_LOCAL_ID");
  721. }
  722. if (reqBody.customAttributes) {
  723. validateSerializedCustomClaims(reqBody.customAttributes);
  724. }
  725. reqBody.deleteAttribute = reqBody.deleteAttribute || [];
  726. for (const attr of reqBody.deleteAttribute) {
  727. if (attr === "PROVIDER" || attr === "RAW_USER_INFO") {
  728. throw new errors_1.NotImplementedError(`deleteAttribute: ${attr}`);
  729. }
  730. }
  731. const updates = {};
  732. let user;
  733. let signInProvider;
  734. let isEmailUpdate = false;
  735. if (reqBody.oobCode) {
  736. const oob = state.validateOobCode(reqBody.oobCode);
  737. (0, errors_1.assert)(oob, "INVALID_OOB_CODE");
  738. switch (oob.requestType) {
  739. case "VERIFY_EMAIL": {
  740. state.deleteOobCode(reqBody.oobCode);
  741. signInProvider = state_1.PROVIDER_PASSWORD;
  742. const maybeUser = state.getUserByEmail(oob.email);
  743. (0, errors_1.assert)(maybeUser, "INVALID_OOB_CODE");
  744. user = maybeUser;
  745. updates.emailVerified = true;
  746. if (oob.email !== user.email) {
  747. updates.email = oob.email;
  748. }
  749. break;
  750. }
  751. case "RECOVER_EMAIL": {
  752. state.deleteOobCode(reqBody.oobCode);
  753. const maybeUser = state.getUserByInitialEmail(oob.email);
  754. (0, errors_1.assert)(maybeUser, "INVALID_OOB_CODE");
  755. (0, errors_1.assert)(!state.getUserByEmail(oob.email), "EMAIL_EXISTS");
  756. user = maybeUser;
  757. if (oob.email !== user.email) {
  758. updates.email = oob.email;
  759. updates.emailVerified = true;
  760. }
  761. break;
  762. }
  763. default:
  764. throw new errors_1.NotImplementedError(oob.requestType);
  765. }
  766. }
  767. else {
  768. if (reqBody.idToken) {
  769. ({ user, signInProvider } = parseIdToken(state, reqBody.idToken));
  770. (0, errors_1.assert)(reqBody.disableUser == null, "OPERATION_NOT_ALLOWED");
  771. }
  772. else {
  773. (0, errors_1.assert)(reqBody.localId, "MISSING_LOCAL_ID");
  774. const maybeUser = state.getUserByLocalId(reqBody.localId);
  775. (0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
  776. user = maybeUser;
  777. }
  778. if (reqBody.email) {
  779. (0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
  780. const newEmail = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
  781. if (newEmail !== user.email) {
  782. (0, errors_1.assert)(!state.getUserByEmail(newEmail), "EMAIL_EXISTS");
  783. updates.email = newEmail;
  784. updates.emailVerified = false;
  785. isEmailUpdate = true;
  786. if (signInProvider !== state_1.PROVIDER_ANONYMOUS && user.email && !user.initialEmail) {
  787. updates.initialEmail = user.email;
  788. }
  789. }
  790. }
  791. if (reqBody.password) {
  792. (0, errors_1.assert)(reqBody.password.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters`);
  793. updates.salt = "fakeSalt" + (0, utils_1.randomId)(20);
  794. updates.passwordHash = hashPassword(reqBody.password, updates.salt);
  795. updates.passwordUpdatedAt = Date.now();
  796. signInProvider = state_1.PROVIDER_PASSWORD;
  797. }
  798. if (reqBody.password || reqBody.validSince || updates.email) {
  799. updates.validSince = (0, utils_1.toUnixTimestamp)(new Date()).toString();
  800. }
  801. if (reqBody.mfa) {
  802. if (reqBody.mfa.enrollments && reqBody.mfa.enrollments.length > 0) {
  803. updates.mfaInfo = getMfaEnrollmentsFromRequest(state, reqBody.mfa.enrollments);
  804. }
  805. else {
  806. updates.mfaInfo = undefined;
  807. }
  808. }
  809. const fieldsToCopy = [
  810. "displayName",
  811. "photoUrl",
  812. ];
  813. if (privileged) {
  814. if (reqBody.disableUser != null) {
  815. updates.disabled = reqBody.disableUser;
  816. }
  817. if (reqBody.phoneNumber && reqBody.phoneNumber !== user.phoneNumber) {
  818. (0, errors_1.assert)((0, utils_1.isValidPhoneNumber)(reqBody.phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
  819. (0, errors_1.assert)(!state.getUserByPhoneNumber(reqBody.phoneNumber), "PHONE_NUMBER_EXISTS");
  820. updates.phoneNumber = reqBody.phoneNumber;
  821. }
  822. fieldsToCopy.push("emailVerified", "customAttributes", "createdAt", "lastLoginAt", "validSince");
  823. }
  824. for (const field of fieldsToCopy) {
  825. if (reqBody[field] != null) {
  826. (0, utils_1.mirrorFieldTo)(updates, field, reqBody);
  827. }
  828. }
  829. for (const attr of reqBody.deleteAttribute) {
  830. switch (attr) {
  831. case "USER_ATTRIBUTE_NAME_UNSPECIFIED":
  832. continue;
  833. case "DISPLAY_NAME":
  834. updates.displayName = undefined;
  835. break;
  836. case "PHOTO_URL":
  837. updates.photoUrl = undefined;
  838. break;
  839. case "PASSWORD":
  840. updates.passwordHash = undefined;
  841. updates.salt = undefined;
  842. break;
  843. case "EMAIL":
  844. updates.email = undefined;
  845. updates.emailVerified = undefined;
  846. updates.emailLinkSignin = undefined;
  847. break;
  848. }
  849. }
  850. if ((_a = reqBody.deleteProvider) === null || _a === void 0 ? void 0 : _a.includes(state_1.PROVIDER_PASSWORD)) {
  851. updates.email = undefined;
  852. updates.emailVerified = undefined;
  853. updates.emailLinkSignin = undefined;
  854. updates.passwordHash = undefined;
  855. updates.salt = undefined;
  856. }
  857. if ((_b = reqBody.deleteProvider) === null || _b === void 0 ? void 0 : _b.includes(state_1.PROVIDER_PHONE)) {
  858. updates.phoneNumber = undefined;
  859. }
  860. }
  861. user = state.updateUserByLocalId(user.localId, updates, {
  862. deleteProviders: reqBody.deleteProvider,
  863. });
  864. if (signInProvider !== state_1.PROVIDER_ANONYMOUS && user.initialEmail && isEmailUpdate) {
  865. if (!emulatorUrl) {
  866. throw new Error("Internal assertion error: missing emulatorUrl param");
  867. }
  868. sendOobForEmailReset(state, user.initialEmail, emulatorUrl);
  869. }
  870. 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, passwordHash: user.passwordHash }, (updates.validSince && signInProvider ? issueTokens(state, user, signInProvider) : {})));
  871. }
  872. exports.setAccountInfoImpl = setAccountInfoImpl;
  873. function sendOobForEmailReset(state, initialEmail, url) {
  874. const oobRecord = createOobRecord(state, initialEmail, url, {
  875. requestType: "RECOVER_EMAIL",
  876. mode: "recoverEmail",
  877. });
  878. logOobMessage(oobRecord);
  879. }
  880. function createOobRecord(state, email, url, params) {
  881. const oobRecord = state.createOob(email, params.requestType, (oobCode) => {
  882. url.pathname = "/emulator/action";
  883. url.searchParams.set("mode", params.mode);
  884. url.searchParams.set("lang", "en");
  885. url.searchParams.set("oobCode", oobCode);
  886. url.searchParams.set("apiKey", "fake-api-key");
  887. if (params.continueUrl) {
  888. url.searchParams.set("continueUrl", params.continueUrl);
  889. }
  890. if (state instanceof state_1.TenantProjectState) {
  891. url.searchParams.set("tenantId", state.tenantId);
  892. }
  893. return url.toString();
  894. });
  895. return oobRecord;
  896. }
  897. function logOobMessage(oobRecord) {
  898. const oobLink = oobRecord.oobLink;
  899. const email = oobRecord.email;
  900. let maybeMessage;
  901. switch (oobRecord.requestType) {
  902. case "EMAIL_SIGNIN":
  903. maybeMessage = `To sign in as ${email}, follow this link: ${oobLink}`;
  904. break;
  905. case "PASSWORD_RESET":
  906. maybeMessage = `To reset the password for ${email}, follow this link: ${oobLink}&newPassword=NEW_PASSWORD_HERE`;
  907. break;
  908. case "VERIFY_EMAIL":
  909. maybeMessage = `To verify the email address ${email}, follow this link: ${oobLink}`;
  910. break;
  911. case "RECOVER_EMAIL":
  912. maybeMessage = `To reset your email address to ${email}, follow this link: ${oobLink}`;
  913. break;
  914. }
  915. if (maybeMessage) {
  916. emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", maybeMessage);
  917. }
  918. }
  919. function signInWithCustomToken(state, reqBody) {
  920. var _a;
  921. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  922. (0, errors_1.assert)(reqBody.token, "MISSING_CUSTOM_TOKEN");
  923. let payload;
  924. if (reqBody.token.startsWith("{")) {
  925. try {
  926. payload = JSON.parse(reqBody.token);
  927. }
  928. catch (_b) {
  929. throw new errors_1.BadRequestError("INVALID_CUSTOM_TOKEN : ((Auth Emulator only accepts strict JSON or JWTs as fake custom tokens.))");
  930. }
  931. }
  932. else {
  933. const decoded = (0, jsonwebtoken_1.decode)(reqBody.token, { complete: true });
  934. if (state instanceof state_1.TenantProjectState) {
  935. (0, errors_1.assert)((decoded === null || decoded === void 0 ? void 0 : decoded.payload.tenant_id) === state.tenantId, "TENANT_ID_MISMATCH");
  936. }
  937. (0, errors_1.assert)(decoded, "INVALID_CUSTOM_TOKEN : Invalid assertion format");
  938. if (decoded.header.alg !== "none") {
  939. 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");
  940. }
  941. (0, errors_1.assert)(decoded.payload.aud === exports.CUSTOM_TOKEN_AUDIENCE, `INVALID_CUSTOM_TOKEN : ((Invalid aud (audience): ${decoded.payload.aud} ` +
  942. "Note: Firebase ID Tokens / third-party tokens cannot be used with signInWithCustomToken.))");
  943. payload = decoded.payload;
  944. }
  945. const localId = (_a = coercePrimitiveToString(payload.uid)) !== null && _a !== void 0 ? _a : coercePrimitiveToString(payload.user_id);
  946. (0, errors_1.assert)(localId, "MISSING_IDENTIFIER");
  947. let extraClaims = {};
  948. if ("claims" in payload) {
  949. validateCustomClaims(payload.claims);
  950. extraClaims = payload.claims;
  951. }
  952. let user = state.getUserByLocalId(localId);
  953. const isNewUser = !user;
  954. const timestamp = new Date();
  955. const updates = {
  956. customAuth: true,
  957. lastLoginAt: timestamp.getTime().toString(),
  958. tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined,
  959. };
  960. if (user) {
  961. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  962. user = state.updateUserByLocalId(localId, updates);
  963. }
  964. else {
  965. updates.createdAt = timestamp.getTime().toString();
  966. user = state.createUserWithLocalId(localId, updates);
  967. if (!user) {
  968. throw new Error(`Internal assertion error: trying to create duplicate localId: ${localId}`);
  969. }
  970. }
  971. return Object.assign({ kind: "identitytoolkit#VerifyCustomTokenResponse", isNewUser }, issueTokens(state, user, state_1.PROVIDER_CUSTOM, { extraClaims }));
  972. }
  973. async function signInWithEmailLink(state, reqBody) {
  974. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  975. (0, errors_1.assert)(state.enableEmailLinkSignin, "OPERATION_NOT_ALLOWED");
  976. const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
  977. (0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
  978. const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
  979. (0, errors_1.assert)(reqBody.oobCode, "MISSING_OOB_CODE");
  980. const oob = state.validateOobCode(reqBody.oobCode);
  981. (0, errors_1.assert)(oob && oob.requestType === "EMAIL_SIGNIN", "INVALID_OOB_CODE");
  982. (0, errors_1.assert)(email === oob.email, "INVALID_EMAIL : The email provided does not match the sign-in email address.");
  983. state.deleteOobCode(reqBody.oobCode);
  984. const userFromEmail = state.getUserByEmail(email);
  985. let user = userFromIdToken || userFromEmail;
  986. const isNewUser = !user;
  987. const timestamp = new Date();
  988. let updates = {
  989. email,
  990. emailVerified: true,
  991. emailLinkSignin: true,
  992. };
  993. if (state instanceof state_1.TenantProjectState) {
  994. updates.tenantId = state.tenantId;
  995. }
  996. let extraClaims;
  997. if (!user) {
  998. updates.createdAt = timestamp.getTime().toString();
  999. const localId = state.generateLocalId();
  1000. const userBeforeCreate = Object.assign({ localId }, updates);
  1001. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, { signInMethod: "emailLink" });
  1002. updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
  1003. user = state.createUserWithLocalId(localId, updates);
  1004. if (!user.disabled && !isMfaEnabled(state, user)) {
  1005. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "emailLink" });
  1006. updates = blockingResponse.updates;
  1007. extraClaims = blockingResponse.extraClaims;
  1008. user = state.updateUserByLocalId(user.localId, updates);
  1009. }
  1010. }
  1011. else {
  1012. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  1013. if (userFromIdToken && userFromEmail) {
  1014. (0, errors_1.assert)(userFromIdToken.localId === userFromEmail.localId, "EMAIL_EXISTS");
  1015. }
  1016. if (!user.disabled && !isMfaEnabled(state, user)) {
  1017. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, Object.assign(Object.assign({}, user), updates), { signInMethod: "emailLink" });
  1018. updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
  1019. extraClaims = blockingResponse.extraClaims;
  1020. }
  1021. user = state.updateUserByLocalId(user.localId, updates);
  1022. }
  1023. const response = {
  1024. kind: "identitytoolkit#EmailLinkSigninResponse",
  1025. email,
  1026. localId: user.localId,
  1027. isNewUser,
  1028. };
  1029. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  1030. if (isMfaEnabled(state, user)) {
  1031. return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
  1032. }
  1033. else {
  1034. user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
  1035. return Object.assign(Object.assign({}, response), issueTokens(state, user, state_1.PROVIDER_PASSWORD, { extraClaims }));
  1036. }
  1037. }
  1038. async function signInWithIdp(state, reqBody) {
  1039. var _a, _b;
  1040. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1041. if (reqBody.returnRefreshToken) {
  1042. throw new errors_1.NotImplementedError("returnRefreshToken is not implemented yet.");
  1043. }
  1044. if (reqBody.pendingIdToken) {
  1045. throw new errors_1.NotImplementedError("pendingIdToken is not implemented yet.");
  1046. }
  1047. const normalizedUri = getNormalizedUri(reqBody);
  1048. const providerId = (_a = normalizedUri.searchParams.get("providerId")) === null || _a === void 0 ? void 0 : _a.toLowerCase();
  1049. (0, errors_1.assert)(providerId, `INVALID_CREDENTIAL_OR_PROVIDER_ID : Invalid IdP response/credential: ${normalizedUri.toString()}`);
  1050. const oauthIdToken = normalizedUri.searchParams.get("id_token") || undefined;
  1051. const oauthAccessToken = normalizedUri.searchParams.get("access_token") || undefined;
  1052. const claims = parseClaims(oauthIdToken) || parseClaims(oauthAccessToken);
  1053. if (!claims) {
  1054. if (oauthIdToken) {
  1055. 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.))`);
  1056. }
  1057. else if (oauthAccessToken) {
  1058. if (providerId === "google.com" || providerId === "apple.com") {
  1059. 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.`);
  1060. }
  1061. else {
  1062. throw new errors_1.NotImplementedError(`The Auth Emulator does not support ${providerId} sign-in with credentials.`);
  1063. }
  1064. }
  1065. else {
  1066. throw new errors_1.NotImplementedError("The Auth Emulator only supports sign-in with credentials (id_token required).");
  1067. }
  1068. }
  1069. let samlResponse;
  1070. let signInAttributes = undefined;
  1071. if (normalizedUri.searchParams.get("SAMLResponse")) {
  1072. samlResponse = JSON.parse(normalizedUri.searchParams.get("SAMLResponse"));
  1073. signInAttributes = (_b = samlResponse.assertion) === null || _b === void 0 ? void 0 : _b.attributeStatements;
  1074. (0, errors_1.assert)(samlResponse.assertion, "INVALID_IDP_RESPONSE ((Missing assertion in SAMLResponse.))");
  1075. (0, errors_1.assert)(samlResponse.assertion.subject, "INVALID_IDP_RESPONSE ((Missing assertion.subject in SAMLResponse.))");
  1076. (0, errors_1.assert)(samlResponse.assertion.subject.nameId, "INVALID_IDP_RESPONSE ((Missing assertion.subject.nameId in SAMLResponse.))");
  1077. }
  1078. let { response, rawId } = fakeFetchUserInfoFromIdp(providerId, claims, samlResponse);
  1079. response.oauthAccessToken =
  1080. oauthAccessToken || `FirebaseAuthEmulatorFakeAccessToken_${providerId}`;
  1081. response.oauthIdToken = oauthIdToken;
  1082. const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
  1083. const userMatchingProvider = state.getUserByProviderRawId(providerId, rawId);
  1084. let accountUpdates;
  1085. try {
  1086. if (userFromIdToken) {
  1087. (0, errors_1.assert)(!userMatchingProvider, "FEDERATED_USER_ID_ALREADY_LINKED");
  1088. ({ accountUpdates, response } = handleLinkIdp(state, response, userFromIdToken));
  1089. }
  1090. else if (state.oneAccountPerEmail) {
  1091. const userMatchingEmail = response.email ? state.getUserByEmail(response.email) : undefined;
  1092. ({ accountUpdates, response } = handleIdpSigninEmailRequired(response, rawId, userMatchingProvider, userMatchingEmail));
  1093. }
  1094. else {
  1095. ({ accountUpdates, response } = handleIdpSigninEmailNotRequired(response, userMatchingProvider));
  1096. }
  1097. }
  1098. catch (err) {
  1099. if (reqBody.returnIdpCredential && err instanceof errors_1.BadRequestError) {
  1100. response.errorMessage = err.message;
  1101. return response;
  1102. }
  1103. else {
  1104. throw err;
  1105. }
  1106. }
  1107. if (response.needConfirmation) {
  1108. return response;
  1109. }
  1110. const providerUserInfo = {
  1111. providerId,
  1112. rawId,
  1113. federatedId: rawId,
  1114. displayName: response.displayName,
  1115. photoUrl: response.photoUrl,
  1116. email: response.email,
  1117. screenName: response.screenName,
  1118. };
  1119. let user;
  1120. let extraClaims;
  1121. const oauthTokens = {
  1122. oauthIdToken: response.oauthIdToken,
  1123. oauthAccessToken: response.oauthAccessToken,
  1124. oauthRefreshToken: response.oauthRefreshToken,
  1125. oauthTokenSecret: response.oauthTokenSecret,
  1126. oauthExpiresIn: coercePrimitiveToString(response.oauthExpireIn),
  1127. };
  1128. if (response.isNewUser) {
  1129. const timestamp = new Date();
  1130. 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 });
  1131. const localId = state.generateLocalId();
  1132. const userBeforeCreate = Object.assign({ localId }, updates);
  1133. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, {
  1134. signInMethod: response.providerId,
  1135. rawUserInfo: response.rawUserInfo,
  1136. signInAttributes: JSON.stringify(signInAttributes),
  1137. }, oauthTokens);
  1138. updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
  1139. user = state.createUserWithLocalId(localId, updates);
  1140. response.localId = user.localId;
  1141. if (!user.disabled && !isMfaEnabled(state, user)) {
  1142. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, {
  1143. signInMethod: response.providerId,
  1144. rawUserInfo: response.rawUserInfo,
  1145. signInAttributes: JSON.stringify(signInAttributes),
  1146. }, oauthTokens);
  1147. updates = blockingResponse.updates;
  1148. extraClaims = blockingResponse.extraClaims;
  1149. user = state.updateUserByLocalId(user.localId, updates);
  1150. }
  1151. }
  1152. else {
  1153. if (!response.localId) {
  1154. throw new Error("Internal assertion error: localId not set for exising user.");
  1155. }
  1156. const maybeUser = state.getUserByLocalId(response.localId);
  1157. (0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
  1158. user = maybeUser;
  1159. let updates = Object.assign({}, accountUpdates.fields);
  1160. if (!user.disabled && !isMfaEnabled(state, user)) {
  1161. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, Object.assign(Object.assign({}, user), updates), {
  1162. signInMethod: response.providerId,
  1163. rawUserInfo: response.rawUserInfo,
  1164. signInAttributes: JSON.stringify(signInAttributes),
  1165. }, oauthTokens);
  1166. extraClaims = blockingResponse.extraClaims;
  1167. updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
  1168. }
  1169. user = state.updateUserByLocalId(response.localId, updates, {
  1170. upsertProviders: [providerUserInfo],
  1171. });
  1172. }
  1173. if (user.email === response.email) {
  1174. response.emailVerified = user.emailVerified;
  1175. }
  1176. if (state instanceof state_1.TenantProjectState) {
  1177. response.tenantId = state.tenantId;
  1178. }
  1179. if (isMfaEnabled(state, user)) {
  1180. return Object.assign(Object.assign({}, response), mfaPending(state, user, providerId));
  1181. }
  1182. else {
  1183. user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
  1184. (0, errors_1.assert)(!(user === null || user === void 0 ? void 0 : user.disabled), "USER_DISABLED");
  1185. return Object.assign(Object.assign({}, response), issueTokens(state, user, providerId, { signInAttributes, extraClaims }));
  1186. }
  1187. }
  1188. async function signInWithPassword(state, reqBody) {
  1189. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1190. (0, errors_1.assert)(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
  1191. (0, errors_1.assert)(reqBody.email !== undefined, "MISSING_EMAIL");
  1192. (0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
  1193. (0, errors_1.assert)(reqBody.password, "MISSING_PASSWORD");
  1194. if (reqBody.captchaResponse || reqBody.captchaChallenge) {
  1195. throw new errors_1.NotImplementedError("captcha unimplemented");
  1196. }
  1197. if (reqBody.idToken || reqBody.pendingIdToken) {
  1198. throw new errors_1.NotImplementedError("idToken / pendingIdToken is no longer in use and unsupported by the Auth Emulator.");
  1199. }
  1200. const email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
  1201. let user = state.getUserByEmail(email);
  1202. (0, errors_1.assert)(user, "EMAIL_NOT_FOUND");
  1203. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  1204. (0, errors_1.assert)(user.passwordHash && user.salt, "INVALID_PASSWORD");
  1205. (0, errors_1.assert)(user.passwordHash === hashPassword(reqBody.password, user.salt), "INVALID_PASSWORD");
  1206. const response = {
  1207. kind: "identitytoolkit#VerifyPasswordResponse",
  1208. registered: true,
  1209. localId: user.localId,
  1210. email,
  1211. };
  1212. if (isMfaEnabled(state, user)) {
  1213. return Object.assign(Object.assign({}, response), mfaPending(state, user, state_1.PROVIDER_PASSWORD));
  1214. }
  1215. else {
  1216. const { updates, extraClaims } = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "password" });
  1217. user = state.updateUserByLocalId(user.localId, Object.assign(Object.assign({}, updates), { lastLoginAt: Date.now().toString() }));
  1218. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  1219. return Object.assign(Object.assign({}, response), issueTokens(state, user, state_1.PROVIDER_PASSWORD, { extraClaims }));
  1220. }
  1221. }
  1222. async function signInWithPhoneNumber(state, reqBody) {
  1223. var _a;
  1224. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1225. (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "UNSUPPORTED_TENANT_OPERATION");
  1226. let phoneNumber;
  1227. if (reqBody.temporaryProof) {
  1228. (0, errors_1.assert)(reqBody.phoneNumber, "MISSING_PHONE_NUMBER");
  1229. const proof = state.validateTemporaryProof(reqBody.temporaryProof, reqBody.phoneNumber);
  1230. (0, errors_1.assert)(proof, "INVALID_TEMPORARY_PROOF");
  1231. ({ phoneNumber } = proof);
  1232. }
  1233. else {
  1234. (0, errors_1.assert)(reqBody.sessionInfo, "MISSING_SESSION_INFO");
  1235. (0, errors_1.assert)(reqBody.code, "MISSING_CODE");
  1236. phoneNumber = verifyPhoneNumber(state, reqBody.sessionInfo, reqBody.code);
  1237. }
  1238. const userFromPhoneNumber = state.getUserByPhoneNumber(phoneNumber);
  1239. const userFromIdToken = reqBody.idToken ? parseIdToken(state, reqBody.idToken).user : undefined;
  1240. if (userFromPhoneNumber && userFromIdToken) {
  1241. if (userFromPhoneNumber.localId !== userFromIdToken.localId) {
  1242. (0, errors_1.assert)(!reqBody.temporaryProof, "PHONE_NUMBER_EXISTS");
  1243. return Object.assign({}, state.createTemporaryProof(phoneNumber));
  1244. }
  1245. }
  1246. let user = userFromIdToken || userFromPhoneNumber;
  1247. const isNewUser = !user;
  1248. const timestamp = new Date();
  1249. let updates = {
  1250. phoneNumber,
  1251. lastLoginAt: timestamp.getTime().toString(),
  1252. };
  1253. let extraClaims;
  1254. if (!user) {
  1255. updates.createdAt = timestamp.getTime().toString();
  1256. const localId = state.generateLocalId();
  1257. const userBeforeCreate = Object.assign({ localId }, updates);
  1258. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, { signInMethod: "phone" });
  1259. updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
  1260. user = state.createUserWithLocalId(localId, updates);
  1261. if (!user.disabled) {
  1262. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: "phone" });
  1263. updates = blockingResponse.updates;
  1264. extraClaims = blockingResponse.extraClaims;
  1265. user = state.updateUserByLocalId(user.localId, updates);
  1266. }
  1267. }
  1268. else {
  1269. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  1270. (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.");
  1271. if (!user.disabled) {
  1272. const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, Object.assign(Object.assign({}, user), updates), { signInMethod: "phone" });
  1273. updates = Object.assign(Object.assign({}, updates), blockingResponse.updates);
  1274. extraClaims = blockingResponse.extraClaims;
  1275. }
  1276. user = state.updateUserByLocalId(user.localId, updates);
  1277. }
  1278. (0, errors_1.assert)(!(user === null || user === void 0 ? void 0 : user.disabled), "USER_DISABLED");
  1279. const tokens = issueTokens(state, user, state_1.PROVIDER_PHONE, {
  1280. extraClaims,
  1281. });
  1282. return Object.assign({ isNewUser,
  1283. phoneNumber, localId: user.localId }, tokens);
  1284. }
  1285. function grantToken(state, reqBody) {
  1286. (0, errors_1.assert)(reqBody.grantType, "MISSING_GRANT_TYPE");
  1287. (0, errors_1.assert)(reqBody.grantType === "refresh_token", "INVALID_GRANT_TYPE");
  1288. (0, errors_1.assert)(reqBody.refreshToken, "MISSING_REFRESH_TOKEN");
  1289. const refreshTokenRecord = state.validateRefreshToken(reqBody.refreshToken);
  1290. (0, errors_1.assert)(!refreshTokenRecord.user.disabled, "USER_DISABLED");
  1291. const tokens = issueTokens(state, refreshTokenRecord.user, refreshTokenRecord.provider, {
  1292. extraClaims: refreshTokenRecord.extraClaims,
  1293. secondFactor: refreshTokenRecord.secondFactor,
  1294. });
  1295. return {
  1296. id_token: tokens.idToken,
  1297. access_token: tokens.idToken,
  1298. expires_in: tokens.expiresIn,
  1299. refresh_token: tokens.refreshToken,
  1300. token_type: "Bearer",
  1301. user_id: refreshTokenRecord.user.localId,
  1302. project_id: state.projectNumber,
  1303. };
  1304. }
  1305. function deleteAllAccountsInProject(state) {
  1306. state.deleteAllAccounts();
  1307. return {};
  1308. }
  1309. function getEmulatorProjectConfig(state) {
  1310. return {
  1311. signIn: {
  1312. allowDuplicateEmails: !state.oneAccountPerEmail,
  1313. },
  1314. };
  1315. }
  1316. function updateEmulatorProjectConfig(state, reqBody, ctx) {
  1317. var _a;
  1318. const updateMask = [];
  1319. if (((_a = reqBody.signIn) === null || _a === void 0 ? void 0 : _a.allowDuplicateEmails) != null) {
  1320. updateMask.push("signIn.allowDuplicateEmails");
  1321. }
  1322. ctx.params.query.updateMask = updateMask.join();
  1323. updateConfig(state, reqBody, ctx);
  1324. return getEmulatorProjectConfig(state);
  1325. }
  1326. function listOobCodesInProject(state) {
  1327. return {
  1328. oobCodes: [...state.listOobCodes()],
  1329. };
  1330. }
  1331. function listVerificationCodesInProject(state) {
  1332. return {
  1333. verificationCodes: [...state.listVerificationCodes()],
  1334. };
  1335. }
  1336. function mfaEnrollmentStart(state, reqBody) {
  1337. var _a, _b;
  1338. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1339. (0, errors_1.assert)((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") &&
  1340. ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
  1341. (0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
  1342. const { user, signInProvider } = parseIdToken(state, reqBody.idToken);
  1343. (0, errors_1.assert)(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
  1344. (0, errors_1.assert)(user.emailVerified, "UNVERIFIED_EMAIL : Need to verify email first before enrolling second factors.");
  1345. (0, errors_1.assert)(reqBody.phoneEnrollmentInfo, "INVALID_ARGUMENT : ((Missing phoneEnrollmentInfo.))");
  1346. const phoneNumber = reqBody.phoneEnrollmentInfo.phoneNumber;
  1347. (0, errors_1.assert)(phoneNumber && (0, utils_1.isValidPhoneNumber)(phoneNumber), "INVALID_PHONE_NUMBER : Invalid format.");
  1348. (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.");
  1349. const { sessionInfo, code } = state.createVerificationCode(phoneNumber);
  1350. emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To enroll MFA with ${phoneNumber}, use the code ${code}.`);
  1351. return {
  1352. phoneSessionInfo: {
  1353. sessionInfo,
  1354. },
  1355. };
  1356. }
  1357. function mfaEnrollmentFinalize(state, reqBody) {
  1358. var _a, _b;
  1359. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1360. (0, errors_1.assert)((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") &&
  1361. ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
  1362. (0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
  1363. let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
  1364. (0, errors_1.assert)(!MFA_INELIGIBLE_PROVIDER.has(signInProvider), "UNSUPPORTED_FIRST_FACTOR : MFA is not available for the given first factor.");
  1365. (0, errors_1.assert)(reqBody.phoneVerificationInfo, "INVALID_ARGUMENT : ((Missing phoneVerificationInfo.))");
  1366. if (reqBody.phoneVerificationInfo.androidVerificationProof) {
  1367. throw new errors_1.NotImplementedError("androidVerificationProof is unsupported!");
  1368. }
  1369. const { code, sessionInfo } = reqBody.phoneVerificationInfo;
  1370. (0, errors_1.assert)(code, "MISSING_CODE");
  1371. (0, errors_1.assert)(sessionInfo, "MISSING_SESSION_INFO");
  1372. const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
  1373. (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.");
  1374. const existingFactors = user.mfaInfo || [];
  1375. const existingIds = new Set();
  1376. for (const { mfaEnrollmentId } of existingFactors) {
  1377. if (mfaEnrollmentId) {
  1378. existingIds.add(mfaEnrollmentId);
  1379. }
  1380. }
  1381. const enrollment = {
  1382. displayName: reqBody.displayName,
  1383. enrolledAt: new Date().toISOString(),
  1384. mfaEnrollmentId: newRandomId(28, existingIds),
  1385. phoneInfo: phoneNumber,
  1386. unobfuscatedPhoneInfo: phoneNumber,
  1387. };
  1388. user = state.updateUserByLocalId(user.localId, {
  1389. mfaInfo: [...existingFactors, enrollment],
  1390. });
  1391. const { idToken, refreshToken } = issueTokens(state, user, signInProvider, {
  1392. secondFactor: { identifier: enrollment.mfaEnrollmentId, provider: state_1.PROVIDER_PHONE },
  1393. });
  1394. return {
  1395. idToken,
  1396. refreshToken,
  1397. };
  1398. }
  1399. function mfaEnrollmentWithdraw(state, reqBody) {
  1400. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1401. (0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
  1402. let { user, signInProvider } = parseIdToken(state, reqBody.idToken);
  1403. (0, errors_1.assert)(user.mfaInfo, "MFA_ENROLLMENT_NOT_FOUND");
  1404. const updatedList = user.mfaInfo.filter((enrollment) => enrollment.mfaEnrollmentId !== reqBody.mfaEnrollmentId);
  1405. (0, errors_1.assert)(updatedList.length < user.mfaInfo.length, "MFA_ENROLLMENT_NOT_FOUND");
  1406. user = state.updateUserByLocalId(user.localId, { mfaInfo: updatedList });
  1407. return Object.assign({}, issueTokens(state, user, signInProvider));
  1408. }
  1409. function mfaSignInStart(state, reqBody) {
  1410. var _a, _b;
  1411. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1412. (0, errors_1.assert)((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") &&
  1413. ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
  1414. (0, errors_1.assert)(reqBody.mfaPendingCredential, "MISSING_MFA_PENDING_CREDENTIAL : Request does not have MFA pending credential.");
  1415. (0, errors_1.assert)(reqBody.mfaEnrollmentId, "MISSING_MFA_ENROLLMENT_ID : No second factor identifier is provided.");
  1416. const { user } = parsePendingCredential(state, reqBody.mfaPendingCredential);
  1417. const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((factor) => factor.mfaEnrollmentId === reqBody.mfaEnrollmentId);
  1418. (0, errors_1.assert)(enrollment, "MFA_ENROLLMENT_NOT_FOUND");
  1419. const phoneNumber = enrollment.unobfuscatedPhoneInfo;
  1420. (0, errors_1.assert)(phoneNumber, "INVALID_ARGUMENT : MFA provider not supported!");
  1421. const { sessionInfo, code } = state.createVerificationCode(phoneNumber);
  1422. emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("BULLET", `To sign in with MFA using ${phoneNumber}, use the code ${code}.`);
  1423. return {
  1424. phoneResponseInfo: {
  1425. sessionInfo,
  1426. },
  1427. };
  1428. }
  1429. async function mfaSignInFinalize(state, reqBody) {
  1430. var _a, _b;
  1431. (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
  1432. (0, errors_1.assert)((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") &&
  1433. ((_a = state.mfaConfig.enabledProviders) === null || _a === void 0 ? void 0 : _a.includes("PHONE_SMS")), "OPERATION_NOT_ALLOWED : SMS based MFA not enabled.");
  1434. (0, errors_1.assert)(reqBody.mfaPendingCredential, "MISSING_CREDENTIAL : Please set MFA Pending Credential.");
  1435. (0, errors_1.assert)(reqBody.phoneVerificationInfo, "INVALID_ARGUMENT : MFA provider not supported!");
  1436. if (reqBody.phoneVerificationInfo.androidVerificationProof) {
  1437. throw new errors_1.NotImplementedError("androidVerificationProof is unsupported!");
  1438. }
  1439. const { code, sessionInfo } = reqBody.phoneVerificationInfo;
  1440. (0, errors_1.assert)(code, "MISSING_CODE");
  1441. (0, errors_1.assert)(sessionInfo, "MISSING_SESSION_INFO");
  1442. const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
  1443. let { user, signInProvider } = parsePendingCredential(state, reqBody.mfaPendingCredential);
  1444. const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber);
  1445. const { updates, extraClaims } = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_SIGN_IN, user, { signInMethod: signInProvider, signInSecondFactor: "phone" });
  1446. user = state.updateUserByLocalId(user.localId, Object.assign(Object.assign({}, updates), { lastLoginAt: Date.now().toString() }));
  1447. (0, errors_1.assert)(enrollment && enrollment.mfaEnrollmentId, "MFA_ENROLLMENT_NOT_FOUND");
  1448. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  1449. const { idToken, refreshToken } = issueTokens(state, user, signInProvider, {
  1450. extraClaims,
  1451. secondFactor: { identifier: enrollment.mfaEnrollmentId, provider: state_1.PROVIDER_PHONE },
  1452. });
  1453. return {
  1454. idToken,
  1455. refreshToken,
  1456. };
  1457. }
  1458. function getConfig(state) {
  1459. (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Can only get top-level configurations on agent projects.))");
  1460. return state.config;
  1461. }
  1462. function updateConfig(state, reqBody, ctx) {
  1463. var _a;
  1464. (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Can only update top-level configurations on agent projects.))");
  1465. for (const event in (_a = reqBody.blockingFunctions) === null || _a === void 0 ? void 0 : _a.triggers) {
  1466. if (Object.prototype.hasOwnProperty.call(reqBody.blockingFunctions.triggers, event)) {
  1467. (0, errors_1.assert)(Object.values(state_1.BlockingFunctionEvents).includes(event), "INVALID_BLOCKING_FUNCTION : ((Event type is invalid.))");
  1468. (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.))");
  1469. }
  1470. }
  1471. return state.updateConfig(reqBody, ctx.params.query.updateMask);
  1472. }
  1473. function coercePrimitiveToString(value) {
  1474. switch (typeof value) {
  1475. case "string":
  1476. return value;
  1477. case "number":
  1478. case "boolean":
  1479. return value.toString();
  1480. default:
  1481. return undefined;
  1482. }
  1483. }
  1484. function redactPasswordHash(user) {
  1485. return user;
  1486. }
  1487. function hashPassword(password, salt) {
  1488. return `fakeHash:salt=${salt}:password=${password}`;
  1489. }
  1490. function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, signInAttributes, } = {}) {
  1491. user = state.updateUserByLocalId(user.localId, { lastRefreshAt: new Date().toISOString() });
  1492. const tenantId = state instanceof state_1.TenantProjectState ? state.tenantId : undefined;
  1493. const expiresInSeconds = 60 * 60;
  1494. const idToken = generateJwt(user, {
  1495. projectId: state.projectId,
  1496. signInProvider,
  1497. expiresInSeconds,
  1498. extraClaims,
  1499. secondFactor,
  1500. tenantId,
  1501. signInAttributes,
  1502. });
  1503. const refreshToken = state.createRefreshTokenFor(user, signInProvider, {
  1504. extraClaims,
  1505. secondFactor,
  1506. });
  1507. return {
  1508. idToken,
  1509. refreshToken,
  1510. expiresIn: expiresInSeconds.toString(),
  1511. };
  1512. }
  1513. function parseIdToken(state, idToken) {
  1514. const decoded = (0, jsonwebtoken_1.decode)(idToken, { complete: true });
  1515. (0, errors_1.assert)(decoded, "INVALID_ID_TOKEN");
  1516. if (decoded.header.alg !== "none") {
  1517. emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("WARN", "Received a signed JWT. Auth Emulator does not validate JWTs and IS NOT SECURE");
  1518. }
  1519. if (decoded.payload.firebase.tenant) {
  1520. (0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Parsed token that belongs to tenant in a non-tenant project.))");
  1521. (0, errors_1.assert)(decoded.payload.firebase.tenant === state.tenantId, "TENANT_ID_MISMATCH");
  1522. }
  1523. const user = state.getUserByLocalId(decoded.payload.user_id);
  1524. (0, errors_1.assert)(user, "USER_NOT_FOUND");
  1525. (0, errors_1.assert)(!user.validSince || decoded.payload.iat >= Number(user.validSince), "TOKEN_EXPIRED");
  1526. (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
  1527. const signInProvider = decoded.payload.firebase.sign_in_provider;
  1528. return { user, signInProvider, payload: decoded.payload };
  1529. }
  1530. function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, tenantId, signInAttributes, }) {
  1531. const identities = {};
  1532. if (user.email) {
  1533. identities["email"] = [user.email];
  1534. }
  1535. if (user.providerUserInfo) {
  1536. for (const providerInfo of user.providerUserInfo) {
  1537. if (providerInfo.providerId &&
  1538. providerInfo.providerId !== state_1.PROVIDER_PASSWORD &&
  1539. providerInfo.rawId) {
  1540. const ids = identities[providerInfo.providerId] || [];
  1541. ids.push(providerInfo.rawId);
  1542. identities[providerInfo.providerId] = ids;
  1543. }
  1544. }
  1545. }
  1546. const customAttributes = JSON.parse(user.customAttributes || "{}");
  1547. 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: {
  1548. identities,
  1549. sign_in_provider: signInProvider,
  1550. second_factor_identifier: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.identifier,
  1551. sign_in_second_factor: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.provider,
  1552. tenant: tenantId,
  1553. sign_in_attributes: signInAttributes,
  1554. } });
  1555. const jwtStr = (0, jsonwebtoken_1.sign)(customPayloadFields, "", {
  1556. algorithm: "none",
  1557. expiresIn: expiresInSeconds,
  1558. subject: user.localId,
  1559. issuer: `https://securetoken.google.com/${projectId}`,
  1560. audience: projectId,
  1561. });
  1562. return jwtStr;
  1563. }
  1564. function getAuthTime(user) {
  1565. if (user.lastLoginAt != null) {
  1566. const millisSinceEpoch = parseInt(user.lastLoginAt, 10);
  1567. const authTime = new Date(millisSinceEpoch);
  1568. if (isNaN(authTime.getTime())) {
  1569. throw new Error(`Internal assertion error: invalid user.lastLoginAt = ${user.lastLoginAt}`);
  1570. }
  1571. return authTime;
  1572. }
  1573. else if (user.lastRefreshAt != null) {
  1574. const authTime = new Date(user.lastRefreshAt);
  1575. if (isNaN(authTime.getTime())) {
  1576. throw new Error(`Internal assertion error: invalid user.lastRefreshAt = ${user.lastRefreshAt}`);
  1577. }
  1578. return authTime;
  1579. }
  1580. else {
  1581. throw new Error(`Internal assertion error: Missing user.lastLoginAt and user.lastRefreshAt`);
  1582. }
  1583. }
  1584. function verifyPhoneNumber(state, sessionInfo, code) {
  1585. const verification = state.getVerificationCodeBySessionInfo(sessionInfo);
  1586. (0, errors_1.assert)(verification, "INVALID_SESSION_INFO");
  1587. (0, errors_1.assert)(verification.code === code, "INVALID_CODE");
  1588. state.deleteVerificationCodeBySessionInfo(sessionInfo);
  1589. return verification.phoneNumber;
  1590. }
  1591. const CUSTOM_ATTRIBUTES_MAX_LENGTH = 1000;
  1592. function validateSerializedCustomClaims(claims) {
  1593. (0, errors_1.assert)(claims.length <= CUSTOM_ATTRIBUTES_MAX_LENGTH, "CLAIMS_TOO_LARGE");
  1594. let parsed;
  1595. try {
  1596. parsed = JSON.parse(claims);
  1597. }
  1598. catch (_a) {
  1599. throw new errors_1.BadRequestError("INVALID_CLAIMS");
  1600. }
  1601. validateCustomClaims(parsed);
  1602. }
  1603. const FORBIDDEN_CUSTOM_CLAIMS = [
  1604. "iss",
  1605. "aud",
  1606. "sub",
  1607. "iat",
  1608. "exp",
  1609. "nbf",
  1610. "jti",
  1611. "nonce",
  1612. "azp",
  1613. "acr",
  1614. "amr",
  1615. "cnf",
  1616. "auth_time",
  1617. "firebase",
  1618. "at_hash",
  1619. "c_hash",
  1620. ];
  1621. function validateCustomClaims(claims) {
  1622. (0, errors_1.assert)(typeof claims === "object" && claims != null && !Array.isArray(claims), "INVALID_CLAIMS");
  1623. for (const reservedField of FORBIDDEN_CUSTOM_CLAIMS) {
  1624. (0, errors_1.assert)(!(reservedField in claims), `FORBIDDEN_CLAIM : ${reservedField}`);
  1625. }
  1626. }
  1627. function newRandomId(length, existingIds) {
  1628. for (let i = 0; i < 10; i++) {
  1629. const id = (0, utils_1.randomId)(length);
  1630. if (!(existingIds === null || existingIds === void 0 ? void 0 : existingIds.has(id))) {
  1631. return id;
  1632. }
  1633. }
  1634. throw new errors_1.InternalError("INTERNAL_ERROR : Failed to generate a random ID after 10 attempts", "INTERNAL");
  1635. }
  1636. function getMfaEnrollmentsFromRequest(state, request, options) {
  1637. const enrollments = [];
  1638. const phoneNumbers = new Set();
  1639. const enrollmentIds = new Set();
  1640. for (const enrollment of request) {
  1641. (0, errors_1.assert)(enrollment.phoneInfo && (0, utils_1.isValidPhoneNumber)(enrollment.phoneInfo), "INVALID_MFA_PHONE_NUMBER : Invalid format.");
  1642. if (!phoneNumbers.has(enrollment.phoneInfo)) {
  1643. const mfaEnrollmentId = (options === null || options === void 0 ? void 0 : options.generateEnrollmentIds)
  1644. ? newRandomId(28, enrollmentIds)
  1645. : enrollment.mfaEnrollmentId;
  1646. (0, errors_1.assert)(mfaEnrollmentId, "INVALID_MFA_ENROLLMENT_ID : mfaEnrollmentId must be defined.");
  1647. (0, errors_1.assert)(!enrollmentIds.has(mfaEnrollmentId), "DUPLICATE_MFA_ENROLLMENT_ID");
  1648. enrollments.push(Object.assign(Object.assign({}, enrollment), { mfaEnrollmentId, unobfuscatedPhoneInfo: enrollment.phoneInfo }));
  1649. phoneNumbers.add(enrollment.phoneInfo);
  1650. enrollmentIds.add(mfaEnrollmentId);
  1651. }
  1652. }
  1653. return state.validateMfaEnrollments(enrollments);
  1654. }
  1655. function getNormalizedUri(reqBody) {
  1656. (0, errors_1.assert)(reqBody.requestUri, "MISSING_REQUEST_URI");
  1657. const normalizedUri = (0, utils_1.parseAbsoluteUri)(reqBody.requestUri);
  1658. (0, errors_1.assert)(normalizedUri, "INVALID_REQUEST_URI");
  1659. if (reqBody.postBody) {
  1660. const postBodyParams = new url_1.URLSearchParams(reqBody.postBody);
  1661. for (const key of postBodyParams.keys()) {
  1662. normalizedUri.searchParams.set(key, postBodyParams.get(key));
  1663. }
  1664. }
  1665. const fragment = normalizedUri.hash.replace(/^#/, "");
  1666. if (fragment) {
  1667. const fragmentParams = new url_1.URLSearchParams(fragment);
  1668. for (const key of fragmentParams.keys()) {
  1669. normalizedUri.searchParams.set(key, fragmentParams.get(key));
  1670. }
  1671. normalizedUri.hash = "";
  1672. }
  1673. return normalizedUri;
  1674. }
  1675. function parseClaims(idTokenOrJsonClaims) {
  1676. if (!idTokenOrJsonClaims) {
  1677. return undefined;
  1678. }
  1679. let claims;
  1680. if (idTokenOrJsonClaims.startsWith("{")) {
  1681. try {
  1682. claims = JSON.parse(idTokenOrJsonClaims);
  1683. }
  1684. catch (_a) {
  1685. 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.))`);
  1686. }
  1687. }
  1688. else {
  1689. const decoded = (0, jsonwebtoken_1.decode)(idTokenOrJsonClaims, { json: true });
  1690. if (!decoded) {
  1691. return undefined;
  1692. }
  1693. claims = decoded;
  1694. }
  1695. (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.))');
  1696. (0, errors_1.assert)(typeof claims.sub === "string", 'INVALID_IDP_RESPONSE : ((The "sub" field must be a string.))');
  1697. return claims;
  1698. }
  1699. function fakeFetchUserInfoFromIdp(providerId, claims, samlResponse) {
  1700. var _a, _b, _c, _d, _e;
  1701. const rawId = claims.sub;
  1702. const email = claims.email ? (0, utils_1.canonicalizeEmailAddress)(claims.email) : undefined;
  1703. const emailVerified = !!claims.email_verified;
  1704. const displayName = claims.name;
  1705. const photoUrl = claims.picture;
  1706. const response = {
  1707. kind: "identitytoolkit#VerifyAssertionResponse",
  1708. context: "",
  1709. providerId,
  1710. displayName,
  1711. fullName: displayName,
  1712. screenName: claims.screen_name,
  1713. email,
  1714. emailVerified,
  1715. photoUrl,
  1716. };
  1717. let federatedId = rawId;
  1718. switch (providerId) {
  1719. case "google.com": {
  1720. federatedId = `https://accounts.google.com/${rawId}`;
  1721. let grantedScopes = "openid https://www.googleapis.com/auth/userinfo.profile";
  1722. if (email) {
  1723. grantedScopes += " https://www.googleapis.com/auth/userinfo.email";
  1724. }
  1725. response.firstName = claims.given_name;
  1726. response.lastName = claims.family_name;
  1727. response.rawUserInfo = JSON.stringify({
  1728. granted_scopes: grantedScopes,
  1729. id: rawId,
  1730. name: displayName,
  1731. given_name: claims.given_name,
  1732. family_name: claims.family_name,
  1733. verified_email: emailVerified,
  1734. locale: "en",
  1735. email,
  1736. picture: photoUrl,
  1737. });
  1738. break;
  1739. }
  1740. case (_a = providerId.match(/^saml\./)) === null || _a === void 0 ? void 0 : _a.input:
  1741. 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;
  1742. response.email = nameId && (0, utils_1.isValidEmailAddress)(nameId) ? nameId : response.email;
  1743. response.emailVerified = true;
  1744. response.rawUserInfo = JSON.stringify((_d = samlResponse === null || samlResponse === void 0 ? void 0 : samlResponse.assertion) === null || _d === void 0 ? void 0 : _d.attributeStatements);
  1745. break;
  1746. case (_e = providerId.match(/^oidc\./)) === null || _e === void 0 ? void 0 : _e.input:
  1747. default:
  1748. response.rawUserInfo = JSON.stringify(claims);
  1749. break;
  1750. }
  1751. response.federatedId = federatedId;
  1752. return { response, rawId };
  1753. }
  1754. function handleLinkIdp(state, response, userFromIdToken) {
  1755. if (state.oneAccountPerEmail && response.email) {
  1756. const userMatchingEmail = state.getUserByEmail(response.email);
  1757. (0, errors_1.assert)(!userMatchingEmail || userMatchingEmail.localId === userFromIdToken.localId, "EMAIL_EXISTS");
  1758. }
  1759. response.localId = userFromIdToken.localId;
  1760. const fields = {};
  1761. if (state.oneAccountPerEmail && response.email && !userFromIdToken.email) {
  1762. fields.email = response.email;
  1763. fields.emailVerified = response.emailVerified;
  1764. }
  1765. if (response.email &&
  1766. response.emailVerified &&
  1767. (fields.email || userFromIdToken.email) === response.email) {
  1768. fields.emailVerified = true;
  1769. }
  1770. return { accountUpdates: { fields }, response };
  1771. }
  1772. function handleIdpSigninEmailNotRequired(response, userMatchingProvider) {
  1773. if (userMatchingProvider) {
  1774. return {
  1775. response: Object.assign(Object.assign({}, response), { localId: userMatchingProvider.localId }),
  1776. accountUpdates: {},
  1777. };
  1778. }
  1779. else {
  1780. return handleIdpSignUp(response, { emailRequired: false });
  1781. }
  1782. }
  1783. function handleIdpSigninEmailRequired(response, rawId, userMatchingProvider, userMatchingEmail) {
  1784. var _a, _b, _c;
  1785. if (userMatchingProvider) {
  1786. return {
  1787. response: Object.assign(Object.assign({}, response), { localId: userMatchingProvider.localId }),
  1788. accountUpdates: {},
  1789. };
  1790. }
  1791. else if (userMatchingEmail) {
  1792. if (response.emailVerified) {
  1793. if ((_a = userMatchingEmail.providerUserInfo) === null || _a === void 0 ? void 0 : _a.some((info) => info.providerId === response.providerId && info.rawId !== rawId)) {
  1794. response.emailRecycled = true;
  1795. }
  1796. response.localId = userMatchingEmail.localId;
  1797. const accountUpdates = {
  1798. fields: {},
  1799. };
  1800. if (!userMatchingEmail.emailVerified) {
  1801. accountUpdates.fields.passwordHash = undefined;
  1802. accountUpdates.fields.phoneNumber = undefined;
  1803. accountUpdates.fields.validSince = (0, utils_1.toUnixTimestamp)(new Date()).toString();
  1804. accountUpdates.deleteProviders = (_b = userMatchingEmail.providerUserInfo) === null || _b === void 0 ? void 0 : _b.map((info) => info.providerId);
  1805. }
  1806. accountUpdates.fields.dateOfBirth = response.dateOfBirth;
  1807. accountUpdates.fields.displayName = response.displayName;
  1808. accountUpdates.fields.language = response.language;
  1809. accountUpdates.fields.photoUrl = response.photoUrl;
  1810. accountUpdates.fields.screenName = response.screenName;
  1811. accountUpdates.fields.emailVerified = true;
  1812. return { response, accountUpdates };
  1813. }
  1814. else {
  1815. response.needConfirmation = true;
  1816. response.localId = userMatchingEmail.localId;
  1817. 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);
  1818. return { response, accountUpdates: {} };
  1819. }
  1820. }
  1821. else {
  1822. return handleIdpSignUp(response, { emailRequired: true });
  1823. }
  1824. }
  1825. function handleIdpSignUp(response, options) {
  1826. const accountUpdates = {
  1827. fields: {
  1828. dateOfBirth: response.dateOfBirth,
  1829. displayName: response.displayName,
  1830. language: response.language,
  1831. photoUrl: response.photoUrl,
  1832. screenName: response.screenName,
  1833. },
  1834. };
  1835. if (options.emailRequired && response.email) {
  1836. accountUpdates.fields.email = response.email;
  1837. accountUpdates.fields.emailVerified = response.emailVerified;
  1838. }
  1839. return {
  1840. response: Object.assign(Object.assign({}, response), { isNewUser: true }),
  1841. accountUpdates,
  1842. };
  1843. }
  1844. function mfaPending(state, user, signInProvider) {
  1845. if (!user.mfaInfo) {
  1846. throw new Error("Internal assertion error: mfaPending called on user without MFA.");
  1847. }
  1848. const pendingCredentialPayload = {
  1849. _AuthEmulatorMfaPendingCredential: "DO NOT MODIFY",
  1850. localId: user.localId,
  1851. signInProvider,
  1852. projectId: state.projectId,
  1853. };
  1854. if (state instanceof state_1.TenantProjectState) {
  1855. pendingCredentialPayload.tenantId = state.tenantId;
  1856. }
  1857. const mfaPendingCredential = Buffer.from(JSON.stringify(pendingCredentialPayload), "utf8").toString("base64");
  1858. return { mfaPendingCredential, mfaInfo: user.mfaInfo.map(redactMfaInfo) };
  1859. }
  1860. function redactMfaInfo(mfaInfo) {
  1861. return {
  1862. displayName: mfaInfo.displayName,
  1863. enrolledAt: mfaInfo.enrolledAt,
  1864. mfaEnrollmentId: mfaInfo.mfaEnrollmentId,
  1865. phoneInfo: mfaInfo.unobfuscatedPhoneInfo
  1866. ? obfuscatePhoneNumber(mfaInfo.unobfuscatedPhoneInfo)
  1867. : undefined,
  1868. };
  1869. }
  1870. function obfuscatePhoneNumber(phoneNumber) {
  1871. const split = phoneNumber.split("");
  1872. let digitsEncountered = 0;
  1873. for (let i = split.length - 1; i >= 0; i--) {
  1874. if (/[0-9]/.test(split[i])) {
  1875. digitsEncountered++;
  1876. if (digitsEncountered > 4) {
  1877. split[i] = "*";
  1878. }
  1879. }
  1880. }
  1881. return split.join("");
  1882. }
  1883. function parsePendingCredential(state, pendingCredential) {
  1884. let pendingCredentialPayload;
  1885. try {
  1886. const json = Buffer.from(pendingCredential, "base64").toString("utf8");
  1887. pendingCredentialPayload = JSON.parse(json);
  1888. }
  1889. catch (_a) {
  1890. (0, errors_1.assert)(false, "((Invalid phoneVerificationInfo.mfaPendingCredential.))");
  1891. }
  1892. (0, errors_1.assert)(pendingCredentialPayload._AuthEmulatorMfaPendingCredential, "((Invalid phoneVerificationInfo.mfaPendingCredential.))");
  1893. (0, errors_1.assert)(pendingCredentialPayload.projectId === state.projectId, "INVALID_PROJECT_ID : Project ID does not match MFA pending credential.");
  1894. if (state instanceof state_1.TenantProjectState) {
  1895. (0, errors_1.assert)(pendingCredentialPayload.tenantId === state.tenantId, "INVALID_PROJECT_ID : Project ID does not match MFA pending credential.");
  1896. }
  1897. const { localId, signInProvider } = pendingCredentialPayload;
  1898. const user = state.getUserByLocalId(localId);
  1899. (0, errors_1.assert)(user, "((User in pendingCredentialPayload does not exist.))");
  1900. return { user, signInProvider };
  1901. }
  1902. function createTenant(state, reqBody) {
  1903. var _a, _b, _c, _d, _e;
  1904. if (!(state instanceof state_1.AgentProjectState)) {
  1905. throw new errors_1.InternalError("INTERNAL_ERROR : Can only create tenant in agent project", "INTERNAL");
  1906. }
  1907. const mfaConfig = (_a = reqBody.mfaConfig) !== null && _a !== void 0 ? _a : {};
  1908. if (!("state" in mfaConfig)) {
  1909. mfaConfig.state = "DISABLED";
  1910. }
  1911. if (!("enabledProviders" in mfaConfig)) {
  1912. mfaConfig.enabledProviders = [];
  1913. }
  1914. const tenant = {
  1915. displayName: reqBody.displayName,
  1916. allowPasswordSignup: (_b = reqBody.allowPasswordSignup) !== null && _b !== void 0 ? _b : false,
  1917. enableEmailLinkSignin: (_c = reqBody.enableEmailLinkSignin) !== null && _c !== void 0 ? _c : false,
  1918. enableAnonymousUser: (_d = reqBody.enableAnonymousUser) !== null && _d !== void 0 ? _d : false,
  1919. disableAuth: (_e = reqBody.disableAuth) !== null && _e !== void 0 ? _e : false,
  1920. mfaConfig: mfaConfig,
  1921. tenantId: "",
  1922. };
  1923. return state.createTenant(tenant);
  1924. }
  1925. function listTenants(state, reqBody, ctx) {
  1926. (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Can only list tenants in agent project.))");
  1927. const pageSize = Math.min(Math.floor(ctx.params.query.pageSize) || 20, 1000);
  1928. const tenants = state.listTenants(ctx.params.query.pageToken);
  1929. let nextPageToken = undefined;
  1930. if (pageSize > 0 && tenants.length >= pageSize) {
  1931. tenants.length = pageSize;
  1932. nextPageToken = tenants[tenants.length - 1].tenantId;
  1933. }
  1934. return {
  1935. nextPageToken,
  1936. tenants,
  1937. };
  1938. }
  1939. function deleteTenant(state) {
  1940. (0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Can only delete tenant on tenant projects.))");
  1941. state.delete();
  1942. return {};
  1943. }
  1944. function getTenant(state) {
  1945. (0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Can only get tenant on tenant projects.))");
  1946. return state.tenantConfig;
  1947. }
  1948. function updateTenant(state, reqBody, ctx) {
  1949. (0, errors_1.assert)(state instanceof state_1.TenantProjectState, "((Can only update tenant on tenant projects.))");
  1950. return state.updateTenant(reqBody, ctx.params.query.updateMask);
  1951. }
  1952. function isMfaEnabled(state, user) {
  1953. var _a;
  1954. return ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") &&
  1955. ((_a = user.mfaInfo) === null || _a === void 0 ? void 0 : _a.length));
  1956. }
  1957. async function fetchBlockingFunction(state, event, user, options = {}, oauthTokens = {}, timeoutMs = 60000) {
  1958. const url = state.getBlockingFunctionUri(event);
  1959. if (!url) {
  1960. return { updates: {} };
  1961. }
  1962. const jwt = generateBlockingFunctionJwt(state, event, url, timeoutMs, user, options, oauthTokens);
  1963. const reqBody = {
  1964. data: {
  1965. jwt,
  1966. },
  1967. };
  1968. const controller = new abort_controller_1.default();
  1969. const timeout = setTimeout(() => {
  1970. controller.abort();
  1971. }, timeoutMs);
  1972. let response;
  1973. let ok;
  1974. let status;
  1975. let text;
  1976. try {
  1977. const res = await (0, node_fetch_1.default)(url, {
  1978. method: "POST",
  1979. headers: { "Content-Type": "application/json" },
  1980. body: JSON.stringify(reqBody),
  1981. signal: controller.signal,
  1982. });
  1983. ok = res.ok;
  1984. status = res.status;
  1985. text = await res.text();
  1986. }
  1987. catch (thrown) {
  1988. const err = thrown instanceof Error ? thrown : new Error(thrown);
  1989. const isAbortError = err.name.includes("AbortError");
  1990. if (isAbortError) {
  1991. throw new errors_1.InternalError(`BLOCKING_FUNCTION_ERROR_RESPONSE : ((Deadline exceeded making request to ${url}.))`, err.message);
  1992. }
  1993. throw new errors_1.InternalError(`BLOCKING_FUNCTION_ERROR_RESPONSE : ((Failed to make request to ${url}.))`, err.message);
  1994. }
  1995. finally {
  1996. clearTimeout(timeout);
  1997. }
  1998. (0, errors_1.assert)(ok, `BLOCKING_FUNCTION_ERROR_RESPONSE : ((HTTP request to ${url} returned HTTP error ${status}: ${text}))`);
  1999. try {
  2000. response = JSON.parse(text);
  2001. }
  2002. catch (thrown) {
  2003. const err = thrown instanceof Error ? thrown : new Error(thrown);
  2004. throw new errors_1.InternalError(`BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response body is not valid JSON.))`, err.message);
  2005. }
  2006. return processBlockingFunctionResponse(event, response);
  2007. }
  2008. function processBlockingFunctionResponse(event, response) {
  2009. let extraClaims;
  2010. const updates = {};
  2011. if (response.userRecord) {
  2012. const userRecord = response.userRecord;
  2013. (0, errors_1.assert)(userRecord.updateMask, "BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response UserRecord is missing updateMask.))");
  2014. const mask = userRecord.updateMask;
  2015. const fields = mask.split(",");
  2016. for (const field of fields) {
  2017. switch (field) {
  2018. case "displayName":
  2019. case "photoUrl":
  2020. updates[field] = coercePrimitiveToString(userRecord[field]);
  2021. break;
  2022. case "disabled":
  2023. case "emailVerified":
  2024. updates[field] = !!userRecord[field];
  2025. break;
  2026. case "customClaims":
  2027. const customClaims = JSON.stringify(userRecord.customClaims);
  2028. validateSerializedCustomClaims(customClaims);
  2029. updates.customAttributes = customClaims;
  2030. break;
  2031. case "sessionClaims":
  2032. if (event !== state_1.BlockingFunctionEvents.BEFORE_SIGN_IN) {
  2033. break;
  2034. }
  2035. try {
  2036. extraClaims = userRecord.sessionClaims;
  2037. }
  2038. catch (_a) {
  2039. throw new errors_1.BadRequestError("BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response has malformed session claims.))");
  2040. }
  2041. break;
  2042. default:
  2043. break;
  2044. }
  2045. }
  2046. }
  2047. return { updates, extraClaims };
  2048. }
  2049. function generateBlockingFunctionJwt(state, event, url, timeoutMs, user, options, oauthTokens) {
  2050. const issuedAt = (0, utils_1.toUnixTimestamp)(new Date());
  2051. const jwt = {
  2052. iss: `https://securetoken.google.com/${state.projectId}`,
  2053. aud: url,
  2054. iat: issuedAt,
  2055. exp: issuedAt + timeoutMs / 100,
  2056. event_id: (0, utils_1.randomBase64UrlStr)(16),
  2057. event_type: event,
  2058. user_agent: "NotYetSupportedInFirebaseAuthEmulator",
  2059. ip_address: "127.0.0.1",
  2060. locale: "en",
  2061. user_record: {
  2062. uid: user.localId,
  2063. email: user.email,
  2064. email_verified: user.emailVerified,
  2065. display_name: user.displayName,
  2066. photo_url: user.photoUrl,
  2067. disabled: user.disabled,
  2068. phone_number: user.phoneNumber,
  2069. custom_claims: JSON.parse(user.customAttributes || "{}"),
  2070. },
  2071. sub: user.localId,
  2072. sign_in_method: options.signInMethod,
  2073. sign_in_second_factor: options.signInSecondFactor,
  2074. sign_in_attributes: options.signInAttributes,
  2075. raw_user_info: options.rawUserInfo,
  2076. };
  2077. if (state instanceof state_1.TenantProjectState) {
  2078. jwt.tenant_id = state.tenantId;
  2079. jwt.user_record.tenant_id = state.tenantId;
  2080. }
  2081. const providerData = [];
  2082. if (user.providerUserInfo) {
  2083. for (const providerUserInfo of user.providerUserInfo) {
  2084. const provider = {
  2085. provider_id: providerUserInfo.providerId,
  2086. display_name: providerUserInfo.displayName,
  2087. photo_url: providerUserInfo.photoUrl,
  2088. email: providerUserInfo.email,
  2089. uid: providerUserInfo.rawId,
  2090. phone_number: providerUserInfo.phoneNumber,
  2091. };
  2092. providerData.push(provider);
  2093. }
  2094. }
  2095. jwt.user_record.provider_data = providerData;
  2096. if (user.mfaInfo) {
  2097. const enrolledFactors = [];
  2098. for (const mfaEnrollment of user.mfaInfo) {
  2099. if (!mfaEnrollment.mfaEnrollmentId) {
  2100. continue;
  2101. }
  2102. const enrolledFactor = {
  2103. uid: mfaEnrollment.mfaEnrollmentId,
  2104. display_name: mfaEnrollment.displayName,
  2105. enrollment_time: mfaEnrollment.enrolledAt,
  2106. phone_number: mfaEnrollment.phoneInfo,
  2107. factor_id: state_1.PROVIDER_PHONE,
  2108. };
  2109. enrolledFactors.push(enrolledFactor);
  2110. }
  2111. jwt.user_record.multi_factor = {
  2112. enrolled_factors: enrolledFactors,
  2113. };
  2114. }
  2115. if (user.lastLoginAt || user.createdAt) {
  2116. jwt.user_record.metadata = {
  2117. last_sign_in_time: user.lastLoginAt,
  2118. creation_time: user.createdAt,
  2119. };
  2120. }
  2121. if (state.shouldForwardCredentialToBlockingFunction("accessToken")) {
  2122. jwt.oauth_access_token = oauthTokens.oauthAccessToken;
  2123. jwt.oauth_token_secret = oauthTokens.oauthTokenSecret;
  2124. jwt.oauth_expires_in = oauthTokens.oauthExpiresIn;
  2125. }
  2126. if (state.shouldForwardCredentialToBlockingFunction("idToken")) {
  2127. jwt.oauth_id_token = oauthTokens.oauthIdToken;
  2128. }
  2129. if (state.shouldForwardCredentialToBlockingFunction("refreshToken")) {
  2130. jwt.oauth_refresh_token = oauthTokens.oauthRefreshToken;
  2131. }
  2132. const jwtStr = (0, jsonwebtoken_1.sign)(jwt, "", {
  2133. algorithm: "none",
  2134. });
  2135. return jwtStr;
  2136. }
  2137. function parseBlockingFunctionJwt(jwt) {
  2138. const decoded = (0, jsonwebtoken_1.decode)(jwt, { json: true });
  2139. (0, errors_1.assert)(decoded, "((Invalid blocking function jwt.))");
  2140. (0, errors_1.assert)(decoded.iss, "((Invalid blocking function jwt, missing `iss` claim.))");
  2141. (0, errors_1.assert)(decoded.aud, "((Invalid blocking function jwt, missing `aud` claim.))");
  2142. (0, errors_1.assert)(decoded.user_record, "((Invalid blocking function jwt, missing `user_record` claim.))");
  2143. return decoded;
  2144. }
  2145. exports.parseBlockingFunctionJwt = parseBlockingFunctionJwt;