@@ -18,6 +18,8 @@ import * as validator from '../utils/validator';
18
18
import { deepCopy } from '../utils/deep-copy' ;
19
19
import { AuthClientErrorCode , FirebaseAuthError } from '../utils/error' ;
20
20
21
+ /** A maximum of 10 test phone number / code pairs can be configured. */
22
+ export const MAXIMUM_TEST_PHONE_NUMBERS = 10 ;
21
23
22
24
/** The filter interface used for listing provider configurations. */
23
25
export interface AuthProviderConfigFilter {
@@ -160,6 +162,212 @@ export interface EmailSignInConfigServerRequest {
160
162
enableEmailLinkSignin ?: boolean ;
161
163
}
162
164
165
+ /** Identifies the public second factor type. */
166
+ export type AuthFactorType = 'phone' ;
167
+
168
+ /** Identifies the server side second factor type. */
169
+ export type AuthFactorServerType = 'PHONE_SMS' ;
170
+
171
+ /** Client Auth factor type to server auth factor type mapping. */
172
+ export const AUTH_FACTOR_CLIENT_TO_SERVER_TYPE : { [ key : string ] : AuthFactorServerType } = {
173
+ phone : 'PHONE_SMS' ,
174
+ } ;
175
+
176
+ /** Server Auth factor type to client auth factor type mapping. */
177
+ export const AUTH_FACTOR_SERVER_TO_CLIENT_TYPE : { [ key : string ] : AuthFactorType } =
178
+ Object . keys ( AUTH_FACTOR_CLIENT_TO_SERVER_TYPE )
179
+ . reduce ( ( res : { [ key : string ] : AuthFactorType } , key ) => {
180
+ res [ AUTH_FACTOR_CLIENT_TO_SERVER_TYPE [ key ] ] = key as AuthFactorType ;
181
+ return res ;
182
+ } , { } ) ;
183
+
184
+ /** Identifies a multi-factor configuration state. */
185
+ export type MultiFactorConfigState = 'ENABLED' | 'DISABLED' ;
186
+
187
+ /**
188
+ * Public API interface representing a multi-factor configuration.
189
+ */
190
+ export interface MultiFactorConfig {
191
+ /**
192
+ * The multi-factor config state.
193
+ */
194
+ state : MultiFactorConfigState ;
195
+
196
+ /**
197
+ * The list of identifiers for enabled second factors.
198
+ * Currently only ‘phone’ is supported.
199
+ */
200
+ factorIds ?: AuthFactorType [ ] ;
201
+ }
202
+
203
+ /** Server side multi-factor configuration. */
204
+ export interface MultiFactorAuthServerConfig {
205
+ state ?: MultiFactorConfigState ;
206
+ enabledProviders ?: AuthFactorServerType [ ] ;
207
+ }
208
+
209
+
210
+ /**
211
+ * Defines the multi-factor config class used to convert client side MultiFactorConfig
212
+ * to a format that is understood by the Auth server.
213
+ */
214
+ export class MultiFactorAuthConfig implements MultiFactorConfig {
215
+ public readonly state : MultiFactorConfigState ;
216
+ public readonly factorIds : AuthFactorType [ ] ;
217
+
218
+ /**
219
+ * Static method to convert a client side request to a MultiFactorAuthServerConfig.
220
+ * Throws an error if validation fails.
221
+ *
222
+ * @param options The options object to convert to a server request.
223
+ * @return The resulting server request.
224
+ */
225
+ public static buildServerRequest ( options : MultiFactorConfig ) : MultiFactorAuthServerConfig {
226
+ const request : MultiFactorAuthServerConfig = { } ;
227
+ MultiFactorAuthConfig . validate ( options ) ;
228
+ if ( Object . prototype . hasOwnProperty . call ( options , 'state' ) ) {
229
+ request . state = options . state ;
230
+ }
231
+ if ( Object . prototype . hasOwnProperty . call ( options , 'factorIds' ) ) {
232
+ ( options . factorIds || [ ] ) . forEach ( ( factorId ) => {
233
+ if ( typeof request . enabledProviders === 'undefined' ) {
234
+ request . enabledProviders = [ ] ;
235
+ }
236
+ request . enabledProviders . push ( AUTH_FACTOR_CLIENT_TO_SERVER_TYPE [ factorId ] ) ;
237
+ } ) ;
238
+ // In case an empty array is passed. Ensure it gets populated so the array is cleared.
239
+ if ( options . factorIds && options . factorIds . length === 0 ) {
240
+ request . enabledProviders = [ ] ;
241
+ }
242
+ }
243
+ return request ;
244
+ }
245
+
246
+ /**
247
+ * Validates the MultiFactorConfig options object. Throws an error on failure.
248
+ *
249
+ * @param options The options object to validate.
250
+ */
251
+ private static validate ( options : MultiFactorConfig ) : void {
252
+ const validKeys = {
253
+ state : true ,
254
+ factorIds : true ,
255
+ } ;
256
+ if ( ! validator . isNonNullObject ( options ) ) {
257
+ throw new FirebaseAuthError (
258
+ AuthClientErrorCode . INVALID_CONFIG ,
259
+ '"MultiFactorConfig" must be a non-null object.' ,
260
+ ) ;
261
+ }
262
+ // Check for unsupported top level attributes.
263
+ for ( const key in options ) {
264
+ if ( ! ( key in validKeys ) ) {
265
+ throw new FirebaseAuthError (
266
+ AuthClientErrorCode . INVALID_CONFIG ,
267
+ `"${ key } " is not a valid MultiFactorConfig parameter.` ,
268
+ ) ;
269
+ }
270
+ }
271
+ // Validate content.
272
+ if ( typeof options . state !== 'undefined' &&
273
+ options . state !== 'ENABLED' &&
274
+ options . state !== 'DISABLED' ) {
275
+ throw new FirebaseAuthError (
276
+ AuthClientErrorCode . INVALID_CONFIG ,
277
+ '"MultiFactorConfig.state" must be either "ENABLED" or "DISABLED".' ,
278
+ ) ;
279
+ }
280
+
281
+ if ( typeof options . factorIds !== 'undefined' ) {
282
+ if ( ! validator . isArray ( options . factorIds ) ) {
283
+ throw new FirebaseAuthError (
284
+ AuthClientErrorCode . INVALID_CONFIG ,
285
+ '"MultiFactorConfig.factorIds" must be an array of valid "AuthFactorTypes".' ,
286
+ ) ;
287
+ }
288
+
289
+ // Validate content of array.
290
+ options . factorIds . forEach ( ( factorId ) => {
291
+ if ( typeof AUTH_FACTOR_CLIENT_TO_SERVER_TYPE [ factorId ] === 'undefined' ) {
292
+ throw new FirebaseAuthError (
293
+ AuthClientErrorCode . INVALID_CONFIG ,
294
+ `"${ factorId } " is not a valid "AuthFactorType".` ,
295
+ ) ;
296
+ }
297
+ } ) ;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * The MultiFactorAuthConfig constructor.
303
+ *
304
+ * @param response The server side response used to initialize the
305
+ * MultiFactorAuthConfig object.
306
+ * @constructor
307
+ */
308
+ constructor ( response : MultiFactorAuthServerConfig ) {
309
+ if ( typeof response . state === 'undefined' ) {
310
+ throw new FirebaseAuthError (
311
+ AuthClientErrorCode . INTERNAL_ERROR ,
312
+ 'INTERNAL ASSERT FAILED: Invalid multi-factor configuration response' ) ;
313
+ }
314
+ this . state = response . state ;
315
+ this . factorIds = [ ] ;
316
+ ( response . enabledProviders || [ ] ) . forEach ( ( enabledProvider ) => {
317
+ // Ignore unsupported types. It is possible the current admin SDK version is
318
+ // not up to date and newer backend types are supported.
319
+ if ( typeof AUTH_FACTOR_SERVER_TO_CLIENT_TYPE [ enabledProvider ] !== 'undefined' ) {
320
+ this . factorIds . push ( AUTH_FACTOR_SERVER_TO_CLIENT_TYPE [ enabledProvider ] ) ;
321
+ }
322
+ } )
323
+ }
324
+
325
+ /** @return The plain object representation of the multi-factor config instance. */
326
+ public toJSON ( ) : object {
327
+ return {
328
+ state : this . state ,
329
+ factorIds : this . factorIds ,
330
+ } ;
331
+ }
332
+ }
333
+
334
+
335
+ /**
336
+ * Validates the provided map of test phone number / code pairs.
337
+ * @param testPhoneNumbers The phone number / code pairs to validate.
338
+ */
339
+ export function validateTestPhoneNumbers (
340
+ testPhoneNumbers : { [ phoneNumber : string ] : string } ,
341
+ ) : void {
342
+ if ( ! validator . isObject ( testPhoneNumbers ) ) {
343
+ throw new FirebaseAuthError (
344
+ AuthClientErrorCode . INVALID_ARGUMENT ,
345
+ '"testPhoneNumbers" must be a map of phone number / code pairs.' ,
346
+ ) ;
347
+ }
348
+ if ( Object . keys ( testPhoneNumbers ) . length > MAXIMUM_TEST_PHONE_NUMBERS ) {
349
+ throw new FirebaseAuthError ( AuthClientErrorCode . MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED ) ;
350
+ }
351
+ for ( const phoneNumber in testPhoneNumbers ) {
352
+ // Validate phone number.
353
+ if ( ! validator . isPhoneNumber ( phoneNumber ) ) {
354
+ throw new FirebaseAuthError (
355
+ AuthClientErrorCode . INVALID_TESTING_PHONE_NUMBER ,
356
+ `"${ phoneNumber } " is not a valid E.164 standard compliant phone number.`
357
+ ) ;
358
+ }
359
+
360
+ // Validate code.
361
+ if ( ! validator . isString ( testPhoneNumbers [ phoneNumber ] ) ||
362
+ ! / ^ [ \d ] { 6 } $ / . test ( testPhoneNumbers [ phoneNumber ] ) ) {
363
+ throw new FirebaseAuthError (
364
+ AuthClientErrorCode . INVALID_TESTING_PHONE_NUMBER ,
365
+ `"${ testPhoneNumbers [ phoneNumber ] } " is not a valid 6 digit code string.`
366
+ ) ;
367
+ }
368
+ }
369
+ }
370
+
163
371
164
372
/**
165
373
* Defines the email sign-in config class used to convert client side EmailSignInConfig
0 commit comments