@@ -13,7 +13,7 @@ import {
13
13
MakeRequired ,
14
14
isValidPhoneNumber ,
15
15
} from "./utils" ;
16
- import { NotImplementedError , assert , BadRequestError } from "./errors" ;
16
+ import { NotImplementedError , assert , BadRequestError , InternalError } from "./errors" ;
17
17
import { Emulators } from "../types" ;
18
18
import { EmulatorLogger } from "../emulatorLogger" ;
19
19
import {
@@ -28,9 +28,7 @@ import {
28
28
PROVIDER_CUSTOM ,
29
29
OobRecord ,
30
30
} from "./state" ;
31
-
32
- import * as schema from "./schema" ;
33
- export type Schemas = schema . components [ "schemas" ] ;
31
+ import { MfaEnrollments , CreateMfaEnrollmentsRequest , Schemas , MfaEnrollment } from "./types" ;
34
32
35
33
/**
36
34
* Create a map from IDs to operations handlers suitable for exegesis.
@@ -166,6 +164,11 @@ function signUp(
166
164
updates . passwordUpdatedAt = Date . now ( ) ;
167
165
updates . validSince = toUnixTimestamp ( new Date ( ) ) . toString ( ) ;
168
166
}
167
+ if ( reqBody . mfaInfo ) {
168
+ updates . mfaInfo = getMfaEnrollmentsFromRequest ( state , reqBody . mfaInfo , {
169
+ generateEnrollmentIds : true ,
170
+ } ) ;
171
+ }
169
172
let user : UserInfo | undefined ;
170
173
if ( reqBody . idToken ) {
171
174
( { user } = parseIdToken ( state , reqBody . idToken ) ) ;
@@ -185,7 +188,6 @@ function signUp(
185
188
return {
186
189
kind : "identitytoolkit#SignupNewUserResponse" ,
187
190
localId : user . localId ,
188
-
189
191
displayName : user . displayName ,
190
192
email : user . email ,
191
193
...( provider ? issueTokens ( state , user , provider ) : { } ) ,
@@ -974,6 +976,17 @@ export function setAccountInfoImpl(
974
976
updates . validSince = toUnixTimestamp ( new Date ( ) ) . toString ( ) ;
975
977
}
976
978
979
+ // if the request specifies an `mfa` key and enrollments are present and non-empty, set the enrollments
980
+ // as the current MFA state for the user. if the `mfa` key is specified and no enrollments are present,
981
+ // clear any existing MFA data for the user. if no `mfa` key is specified, MFA is left unchanged.
982
+ if ( reqBody . mfa ) {
983
+ if ( reqBody . mfa . enrollments && reqBody . mfa . enrollments . length > 0 ) {
984
+ updates . mfaInfo = getMfaEnrollmentsFromRequest ( state , reqBody . mfa . enrollments ) ;
985
+ } else {
986
+ updates . mfaInfo = undefined ;
987
+ }
988
+ }
989
+
977
990
// Copy profile properties to updates, if they're specified.
978
991
const fieldsToCopy : ( keyof typeof reqBody & keyof typeof updates ) [ ] = [
979
992
"displayName" ,
@@ -1203,6 +1216,11 @@ function signInWithCustomToken(
1203
1216
throw new Error ( `Internal assertion error: trying to create duplicate localId: ${ localId } ` ) ;
1204
1217
}
1205
1218
}
1219
+
1220
+ if ( user . mfaInfo ) {
1221
+ throw new NotImplementedError ( "MFA Login not yet implemented." ) ;
1222
+ }
1223
+
1206
1224
return {
1207
1225
kind : "identitytoolkit#VerifyCustomTokenResponse" ,
1208
1226
isNewUser,
@@ -1248,6 +1266,10 @@ function signInWithEmailLink(
1248
1266
user = state . updateUserByLocalId ( user . localId , updates ) ;
1249
1267
}
1250
1268
1269
+ if ( user . mfaInfo ) {
1270
+ throw new NotImplementedError ( "MFA Login not yet implemented." ) ;
1271
+ }
1272
+
1251
1273
const tokens = issueTokens ( state , user , PROVIDER_PASSWORD ) ;
1252
1274
return {
1253
1275
kind : "identitytoolkit#EmailLinkSigninResponse" ,
@@ -1385,6 +1407,10 @@ function signInWithIdp(
1385
1407
) ;
1386
1408
}
1387
1409
1410
+ if ( user . mfaInfo ) {
1411
+ throw new NotImplementedError ( "MFA Login not yet implemented." ) ;
1412
+ }
1413
+
1388
1414
if ( user . email === response . email ) {
1389
1415
response . emailVerified = user . emailVerified ;
1390
1416
}
@@ -1414,6 +1440,10 @@ function signInWithPassword(
1414
1440
assert ( user . passwordHash && user . salt , "INVALID_PASSWORD" ) ;
1415
1441
assert ( user . passwordHash === hashPassword ( reqBody . password , user . salt ) , "INVALID_PASSWORD" ) ;
1416
1442
1443
+ if ( user . mfaInfo ) {
1444
+ throw new NotImplementedError ( "MFA Login not yet implemented." ) ;
1445
+ }
1446
+
1417
1447
const tokens = issueTokens ( state , user , PROVIDER_PASSWORD ) ;
1418
1448
1419
1449
return {
@@ -1477,6 +1507,10 @@ function signInWithPhoneNumber(
1477
1507
user = state . updateUserByLocalId ( user . localId , updates ) ;
1478
1508
}
1479
1509
1510
+ if ( user . mfaInfo ) {
1511
+ throw new NotImplementedError ( "MFA Login not yet implemented." ) ;
1512
+ }
1513
+
1480
1514
const tokens = issueTokens ( state , user , PROVIDER_PHONE ) ;
1481
1515
1482
1516
return {
@@ -1767,6 +1801,50 @@ function validateCustomClaims(claims: unknown): asserts claims is Record<string,
1767
1801
}
1768
1802
}
1769
1803
1804
+ // generates a new random ID, checking against an optional set of "existing ids" for
1805
+ // uniqueness. if a unique ID cannot be generated in 10 tries, an internal error is
1806
+ // thrown. the ID generated by this method is not added to the set provided to this
1807
+ // method, callers must manage their own state.
1808
+ function newRandomId ( length : number , existingIds ?: Set < string > ) : string {
1809
+ for ( let i = 0 ; i < 10 ; i ++ ) {
1810
+ const id = randomId ( length ) ;
1811
+ if ( ! existingIds ?. has ( id ) ) {
1812
+ return id ;
1813
+ }
1814
+ }
1815
+ throw new InternalError (
1816
+ "INTERNAL_ERROR : Failed to generate a random ID after 10 attempts" ,
1817
+ "INTERNAL"
1818
+ ) ;
1819
+ }
1820
+
1821
+ function getMfaEnrollmentsFromRequest (
1822
+ state : ProjectState ,
1823
+ request : MfaEnrollments ,
1824
+ options ?: { generateEnrollmentIds : boolean }
1825
+ ) : MfaEnrollments {
1826
+ const enrollments : MfaEnrollments = [ ] ;
1827
+ const phoneNumbers : Set < string > = new Set < string > ( ) ;
1828
+ const enrollmentIds : Set < string > = new Set < string > ( ) ;
1829
+ for ( const enrollment of request ) {
1830
+ assert (
1831
+ enrollment . phoneInfo && isValidPhoneNumber ( enrollment . phoneInfo ) ,
1832
+ "INVALID_MFA_PHONE_NUMBER : Invalid format."
1833
+ ) ;
1834
+ if ( ! phoneNumbers . has ( enrollment . phoneInfo ) ) {
1835
+ const mfaEnrollmentId = options ?. generateEnrollmentIds
1836
+ ? newRandomId ( 28 , enrollmentIds )
1837
+ : enrollment . mfaEnrollmentId ;
1838
+ assert ( mfaEnrollmentId , "INVALID_MFA_ENROLLMENT_ID : mfaEnrollmentId must be defined." ) ;
1839
+ assert ( ! enrollmentIds . has ( mfaEnrollmentId ) , "DUPLICATE_MFA_ENROLLMENT_ID" ) ;
1840
+ enrollments . push ( { ...enrollment , mfaEnrollmentId } ) ;
1841
+ phoneNumbers . add ( enrollment . phoneInfo ) ;
1842
+ enrollmentIds . add ( mfaEnrollmentId ) ;
1843
+ }
1844
+ }
1845
+ return state . validateMfaEnrollments ( enrollments ) ;
1846
+ }
1847
+
1770
1848
function getNormalizedUri ( reqBody : {
1771
1849
requestUri ?: string | undefined ;
1772
1850
postBody ?: string | undefined ;
0 commit comments