|
8 | 8 | sendSignInLinkToEmail, |
9 | 9 | getAdditionalUserInfo, |
10 | 10 | multiFactor, |
| 11 | + getMultiFactorResolver, |
| 12 | + TotpMultiFactorGenerator, |
11 | 13 | createUserWithEmailAndPassword, |
12 | 14 | signInWithEmailAndPassword, |
13 | 15 | isSignInWithEmailLink, |
@@ -70,6 +72,15 @@ function rejectPromiseWithCodeAndMessage(code, message) { |
70 | 72 | return rejectPromise(getWebError({ code: `auth/${code}`, message })); |
71 | 73 | } |
72 | 74 |
|
| 75 | +function rejectWithCodeAndMessage(code, message) { |
| 76 | + return Promise.reject( |
| 77 | + getWebError({ |
| 78 | + code, |
| 79 | + message, |
| 80 | + }), |
| 81 | + ); |
| 82 | +} |
| 83 | + |
73 | 84 | /** |
74 | 85 | * Returns a structured error object. |
75 | 86 | * @param {error} error The error object. |
@@ -102,7 +113,9 @@ function userToObject(user) { |
102 | 113 | tenantId: user.tenantId !== null && user.tenantId !== '' ? user.tenantId : null, |
103 | 114 | providerData: user.providerData.map(userInfoToObject), |
104 | 115 | metadata: userMetadataToObject(user.metadata), |
105 | | - multiFactor: multiFactor(user).enrolledFactors.map(multiFactorInfoToObject), |
| 116 | + multiFactor: { |
| 117 | + enrolledFactors: multiFactor(user).enrolledFactors.map(multiFactorInfoToObject), |
| 118 | + }, |
106 | 119 | }; |
107 | 120 | } |
108 | 121 |
|
@@ -222,6 +235,7 @@ const instances = {}; |
222 | 235 | const authStateListeners = {}; |
223 | 236 | const idTokenListeners = {}; |
224 | 237 | const sessionMap = new Map(); |
| 238 | +const totpSecretMap = new Map(); |
225 | 239 | let sessionId = 0; |
226 | 240 |
|
227 | 241 | // Returns a cached Firestore instance. |
@@ -441,11 +455,28 @@ export default { |
441 | 455 | * @returns {Promise<object>} - The result of the sign in. |
442 | 456 | */ |
443 | 457 | async signInWithEmailAndPassword(appName, email, password) { |
444 | | - return guard(async () => { |
445 | | - const auth = getCachedAuthInstance(appName); |
446 | | - const credential = await signInWithEmailAndPassword(auth, email, password); |
| 458 | + // The default guard / getWebError process doesn't work well here, |
| 459 | + // since it creates a new error object that is then passed through |
| 460 | + // a native module proxy and gets processed again. |
| 461 | + // We need lots of information from the error so that MFA will work |
| 462 | + // later if needed. So we handle the error custom here. |
| 463 | + // return guard(async () => { |
| 464 | + try { |
| 465 | + const credential = await signInWithEmailAndPassword( |
| 466 | + getCachedAuthInstance(appName), |
| 467 | + email, |
| 468 | + password, |
| 469 | + ); |
447 | 470 | return authResultToObject(credential); |
448 | | - }); |
| 471 | + } catch (e) { |
| 472 | + e.userInfo = { |
| 473 | + code: e.code.split('/')[1], |
| 474 | + message: e.message, |
| 475 | + customData: e.customData, |
| 476 | + }; |
| 477 | + throw e; |
| 478 | + } |
| 479 | + // }); |
449 | 480 | }, |
450 | 481 |
|
451 | 482 | /** |
@@ -991,6 +1022,104 @@ export default { |
991 | 1022 | }); |
992 | 1023 | }, |
993 | 1024 |
|
| 1025 | + /** |
| 1026 | + * Get a MultiFactorResolver from the underlying SDK |
| 1027 | + * @param {*} _appName the name of the app to get the auth instance for |
| 1028 | + * @param {*} uid the uid of the TOTP MFA attempt |
| 1029 | + * @param {*} code the code from the user TOTP app |
| 1030 | + * @return TotpMultiFactorAssertion to use for resolving |
| 1031 | + */ |
| 1032 | + assertionForSignIn(_appName, uid, code) { |
| 1033 | + return TotpMultiFactorGenerator.assertionForSignIn(uid, code); |
| 1034 | + }, |
| 1035 | + |
| 1036 | + /** |
| 1037 | + * Get a MultiFactorResolver from the underlying SDK |
| 1038 | + * @param {*} appName the name of the app to get the auth instance for |
| 1039 | + * @param {*} error the MFA error returned from initial factor login attempt |
| 1040 | + * @return MultiFactorResolver to use for verifying the second factor |
| 1041 | + */ |
| 1042 | + getMultiFactorResolver(appName, error) { |
| 1043 | + return getMultiFactorResolver(getCachedAuthInstance(appName), error); |
| 1044 | + }, |
| 1045 | + |
| 1046 | + /** |
| 1047 | + * generate a TOTP secret |
| 1048 | + * @param {*} _appName - The name of the app to get the auth instance for. |
| 1049 | + * @param {*} session - The MultiFactorSession to associate with the secret |
| 1050 | + * @returns object with secretKey to associate with TotpSecret |
| 1051 | + */ |
| 1052 | + async generateTotpSecret(_appName, session) { |
| 1053 | + return guard(async () => { |
| 1054 | + const totpSecret = await TotpMultiFactorGenerator.generateSecret(sessionMap.get(session)); |
| 1055 | + totpSecretMap.set(totpSecret.secretKey, totpSecret); |
| 1056 | + return { secretKey: totpSecret.secretKey }; |
| 1057 | + }); |
| 1058 | + }, |
| 1059 | + |
| 1060 | + /** |
| 1061 | + * unenroll from TOTP |
| 1062 | + * @param {*} appName - The name of the app to get the auth instance for. |
| 1063 | + * @param {*} enrollmentId - The ID to associate with the enrollment |
| 1064 | + * @returns |
| 1065 | + */ |
| 1066 | + async unenrollMultiFactor(appName, enrollmentId) { |
| 1067 | + return guard(async () => { |
| 1068 | + const auth = getCachedAuthInstance(appName); |
| 1069 | + if (auth.currentUser === null) { |
| 1070 | + return promiseNoUser(true); |
| 1071 | + } |
| 1072 | + await multiFactor(auth.currentUser).unenroll(enrollmentId); |
| 1073 | + }); |
| 1074 | + }, |
| 1075 | + |
| 1076 | + /** |
| 1077 | + * finalize a TOTP enrollment |
| 1078 | + * @param {*} appName - The name of the app to get the auth instance for. |
| 1079 | + * @param {*} secretKey - The secretKey to associate native TotpSecret |
| 1080 | + * @param {*} verificationCode - The TOTP to verify |
| 1081 | + * @param {*} displayName - The name to associate as a hint |
| 1082 | + * @returns |
| 1083 | + */ |
| 1084 | + async finalizeTotpEnrollment(appName, secretKey, verificationCode, displayName) { |
| 1085 | + return guard(async () => { |
| 1086 | + const auth = getCachedAuthInstance(appName); |
| 1087 | + if (auth.currentUser === null) { |
| 1088 | + return promiseNoUser(true); |
| 1089 | + } |
| 1090 | + const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment( |
| 1091 | + totpSecretMap.get(secretKey), |
| 1092 | + verificationCode, |
| 1093 | + ); |
| 1094 | + await multiFactor(auth.currentUser).enroll(multiFactorAssertion, displayName); |
| 1095 | + }); |
| 1096 | + }, |
| 1097 | + |
| 1098 | + /** |
| 1099 | + * generate a TOTP QR Code URL |
| 1100 | + * @param {*} _appName - The name of the app to get the auth instance for. |
| 1101 | + * @param {*} secretKey - The secretKey to associate with the TotpSecret |
| 1102 | + * @param {*} accountName - The account name to use in auth app |
| 1103 | + * @param {*} issuer - The issuer to use in auth app |
| 1104 | + * @returns QR Code URL |
| 1105 | + */ |
| 1106 | + generateQrCodeUrl(_appName, secretKey, accountName, issuer) { |
| 1107 | + return totpSecretMap.get(secretKey).generateQrCodeUrl(accountName, issuer); |
| 1108 | + }, |
| 1109 | + |
| 1110 | + /** |
| 1111 | + * open a QR Code URL in an app directly |
| 1112 | + * @param {*} appName - The name of the app to get the auth instance for. |
| 1113 | + * @param {*} qrCodeUrl the URL to open in the app, from generateQrCodeUrl |
| 1114 | + * @throws Error not supported in this environment |
| 1115 | + */ |
| 1116 | + openInOtpApp() { |
| 1117 | + return rejectWithCodeAndMessage( |
| 1118 | + 'unsupported', |
| 1119 | + 'This operation is not supported in this environment.', |
| 1120 | + ); |
| 1121 | + }, |
| 1122 | + |
994 | 1123 | /* ---------------------- |
995 | 1124 | * other methods |
996 | 1125 | * ---------------------- */ |
|
0 commit comments