@@ -86,6 +86,16 @@ const FIREBASE_AUTH_TENANT_URL_FORMAT = FIREBASE_AUTH_BASE_URL_FORMAT.replace(
86
86
const MAX_LIST_TENANT_PAGE_SIZE = 1000 ;
87
87
88
88
89
+ /**
90
+ * Enum for the user write operation type.
91
+ */
92
+ enum WriteOperationType {
93
+ Create = 'create' ,
94
+ Update = 'update' ,
95
+ Upload = 'upload' ,
96
+ }
97
+
98
+
89
99
/** Defines a base utility to help with resource URL construction. */
90
100
class AuthResourceUrlBuilder {
91
101
protected urlFormat : string ;
@@ -157,8 +167,9 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder {
157
167
* an error is thrown.
158
168
*
159
169
* @param request The AuthFactorInfo request object.
170
+ * @param writeOperationType The write operation type.
160
171
*/
161
- function validateAuthFactorInfo ( request : AuthFactorInfo ) {
172
+ function validateAuthFactorInfo ( request : AuthFactorInfo , writeOperationType : WriteOperationType ) {
162
173
const validKeys = {
163
174
mfaEnrollmentId : true ,
164
175
displayName : true ,
@@ -171,7 +182,12 @@ function validateAuthFactorInfo(request: AuthFactorInfo) {
171
182
delete request [ key ] ;
172
183
}
173
184
}
174
- if ( ! validator . isNonEmptyString ( request . mfaEnrollmentId ) ) {
185
+ // No enrollment ID is available for signupNewUser. Use another identifier.
186
+ const authFactorInfoIdentifier =
187
+ request . mfaEnrollmentId || request . phoneInfo || JSON . stringify ( request ) ;
188
+ const uidRequired = writeOperationType !== WriteOperationType . Create ;
189
+ if ( ( typeof request . mfaEnrollmentId !== 'undefined' || uidRequired ) &&
190
+ ! validator . isNonEmptyString ( request . mfaEnrollmentId ) ) {
175
191
throw new FirebaseAuthError (
176
192
AuthClientErrorCode . INVALID_UID ,
177
193
`The second factor "uid" must be a valid non-empty string.` ,
@@ -181,15 +197,15 @@ function validateAuthFactorInfo(request: AuthFactorInfo) {
181
197
! validator . isString ( request . displayName ) ) {
182
198
throw new FirebaseAuthError (
183
199
AuthClientErrorCode . INVALID_DISPLAY_NAME ,
184
- `The second factor "displayName" for "${ request . mfaEnrollmentId } " must be a valid string.` ,
200
+ `The second factor "displayName" for "${ authFactorInfoIdentifier } " must be a valid string.` ,
185
201
) ;
186
202
}
187
203
// enrolledAt must be a valid UTC date string.
188
204
if ( typeof request . enrolledAt !== 'undefined' &&
189
205
! validator . isISODateString ( request . enrolledAt ) ) {
190
206
throw new FirebaseAuthError (
191
207
AuthClientErrorCode . INVALID_ENROLLMENT_TIME ,
192
- `The second factor "enrollmentTime" for "${ request . mfaEnrollmentId } " must be a valid ` +
208
+ `The second factor "enrollmentTime" for "${ authFactorInfoIdentifier } " must be a valid ` +
193
209
`UTC date string.` ) ;
194
210
}
195
211
// Validate required fields depending on second factor type.
@@ -198,7 +214,7 @@ function validateAuthFactorInfo(request: AuthFactorInfo) {
198
214
if ( ! validator . isPhoneNumber ( request . phoneInfo ) ) {
199
215
throw new FirebaseAuthError (
200
216
AuthClientErrorCode . INVALID_PHONE_NUMBER ,
201
- `The second factor "phoneNumber" for "${ request . mfaEnrollmentId } " must be a non-empty ` +
217
+ `The second factor "phoneNumber" for "${ authFactorInfoIdentifier } " must be a non-empty ` +
202
218
`E.164 standard compliant identifier string.` ) ;
203
219
}
204
220
} else {
@@ -275,10 +291,11 @@ function validateProviderUserInfo(request: any) {
275
291
* are removed from the original request. If an invalid field is passed
276
292
* an error is thrown.
277
293
*
278
- * @param { any } request The create/edit request object.
279
- * @param { boolean= } uploadAccountRequest Whether to validate as an uploadAccount request .
294
+ * @param request The create/edit request object.
295
+ * @param writeOperationType The write operation type .
280
296
*/
281
- function validateCreateEditRequest ( request : any , uploadAccountRequest : boolean = false ) {
297
+ function validateCreateEditRequest ( request : any , writeOperationType : WriteOperationType ) {
298
+ const uploadAccountRequest = writeOperationType === WriteOperationType . Upload ;
282
299
// Hash set of whitelisted parameters.
283
300
const validKeys = {
284
301
displayName : true ,
@@ -458,7 +475,7 @@ function validateCreateEditRequest(request: any, uploadAccountRequest: boolean =
458
475
throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_ENROLLED_FACTORS ) ;
459
476
}
460
477
enrollments . forEach ( ( authFactorInfoEntry : AuthFactorInfo ) => {
461
- validateAuthFactorInfo ( authFactorInfoEntry ) ;
478
+ validateAuthFactorInfo ( authFactorInfoEntry , writeOperationType ) ;
462
479
} ) ;
463
480
}
464
481
}
@@ -559,7 +576,7 @@ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('/accounts:update'
559
576
AuthClientErrorCode . INVALID_ARGUMENT ,
560
577
'"tenantId" is an invalid "UpdateRequest" property.' ) ;
561
578
}
562
- validateCreateEditRequest ( request ) ;
579
+ validateCreateEditRequest ( request , WriteOperationType . Update ) ;
563
580
} )
564
581
// Set response validator.
565
582
. setResponseValidator ( ( response : any ) => {
@@ -596,7 +613,7 @@ export const FIREBASE_AUTH_SIGN_UP_NEW_USER = new ApiSettings('/accounts', 'POST
596
613
AuthClientErrorCode . INVALID_ARGUMENT ,
597
614
'"tenantId" is an invalid "CreateRequest" property.' ) ;
598
615
}
599
- validateCreateEditRequest ( request ) ;
616
+ validateCreateEditRequest ( request , WriteOperationType . Create ) ;
600
617
} )
601
618
// Set response validator.
602
619
. setResponseValidator ( ( response : any ) => {
@@ -912,7 +929,7 @@ export abstract class AbstractAuthRequestHandler {
912
929
// No need to validate raw request or raw response as this is done in UserImportBuilder.
913
930
const userImportBuilder = new UserImportBuilder ( users , options , ( userRequest : any ) => {
914
931
// Pass true to validate the uploadAccount specific fields.
915
- validateCreateEditRequest ( userRequest , true ) ;
932
+ validateCreateEditRequest ( userRequest , WriteOperationType . Upload ) ;
916
933
} ) ;
917
934
const request = userImportBuilder . buildRequest ( ) ;
918
935
// Fail quickly if more users than allowed are to be imported.
@@ -1145,6 +1162,32 @@ export abstract class AbstractAuthRequestHandler {
1145
1162
request . localId = request . uid ;
1146
1163
delete request . uid ;
1147
1164
}
1165
+ // Construct mfa related user data.
1166
+ if ( validator . isNonNullObject ( request . multiFactor ) ) {
1167
+ if ( validator . isNonEmptyArray ( request . multiFactor . enrolledFactors ) ) {
1168
+ const mfaInfo : AuthFactorInfo [ ] = [ ] ;
1169
+ try {
1170
+ request . multiFactor . enrolledFactors . forEach ( ( multiFactorInfo : any ) => {
1171
+ // Enrollment time and uid are not allowed for signupNewUser endpoint.
1172
+ // They will automatically be provisioned server side.
1173
+ if ( multiFactorInfo . enrollmentTime ) {
1174
+ throw new FirebaseAuthError (
1175
+ AuthClientErrorCode . INVALID_ARGUMENT ,
1176
+ '"enrollmentTime" is not supported when adding second factors via "createUser()"' ) ;
1177
+ } else if ( multiFactorInfo . uid ) {
1178
+ throw new FirebaseAuthError (
1179
+ AuthClientErrorCode . INVALID_ARGUMENT ,
1180
+ '"uid" is not supported when adding second factors via "createUser()"' ) ;
1181
+ }
1182
+ mfaInfo . push ( convertMultiFactorInfoToServerFormat ( multiFactorInfo ) ) ;
1183
+ } ) ;
1184
+ } catch ( e ) {
1185
+ return Promise . reject ( e ) ;
1186
+ }
1187
+ request . mfaInfo = mfaInfo ;
1188
+ }
1189
+ delete request . multiFactor ;
1190
+ }
1148
1191
return this . invokeRequestHandler ( this . getAuthUrlBuilder ( ) , FIREBASE_AUTH_SIGN_UP_NEW_USER , request )
1149
1192
. then ( ( response : any ) => {
1150
1193
// Return the user id.
0 commit comments