15
15
*/
16
16
17
17
import { deepCopy } from '../utils/deep-copy' ;
18
+ import { isNonNullObject } from '../utils/validator' ;
18
19
import * as utils from '../utils' ;
19
20
import { AuthClientErrorCode , FirebaseAuthError } from '../utils/error' ;
20
21
@@ -26,8 +27,8 @@ const B64_REDACTED = Buffer.from('REDACTED').toString('base64');
26
27
/**
27
28
* Parses a time stamp string or number and returns the corresponding date if valid.
28
29
*
29
- * @param { any } time The unix timestamp string or number in milliseconds.
30
- * @return { string } The corresponding date as a UTC string, if valid.
30
+ * @param time The unix timestamp string or number in milliseconds.
31
+ * @return The corresponding date as a UTC string, if valid.
31
32
*/
32
33
function parseDate ( time : any ) : string {
33
34
try {
@@ -57,19 +58,219 @@ export interface CreateRequest extends UpdateRequest {
57
58
uid ?: string ;
58
59
}
59
60
61
+ export interface AuthFactorInfo {
62
+ mfaEnrollmentId : string ;
63
+ displayName ?: string ;
64
+ phoneInfo ?: string ;
65
+ enrolledAt ?: string ;
66
+ [ key : string ] : any ;
67
+ }
68
+
69
+ export interface ProviderUserInfo {
70
+ rawId : string ;
71
+ displayName ?: string ;
72
+ email ?: string ;
73
+ photoUrl ?: string ;
74
+ phoneNumber ?: string ;
75
+ providerId : string ;
76
+ federatedId ?: string ;
77
+ }
78
+
79
+ export interface GetAccountInfoUserResponse {
80
+ localId : string ;
81
+ email ?: string ;
82
+ emailVerified ?: boolean ;
83
+ phoneNumber ?: string ;
84
+ displayName ?: string ;
85
+ photoUrl ?: string ;
86
+ disabled ?: boolean ;
87
+ passwordHash ?: string ;
88
+ salt ?: string ;
89
+ customAttributes ?: string ;
90
+ validSince ?: string ;
91
+ tenantId ?: string ;
92
+ providerUserInfo ?: ProviderUserInfo [ ] ;
93
+ mfaInfo ?: AuthFactorInfo [ ] ;
94
+ createdAt ?: string ;
95
+ lastLoginAt ?: string ;
96
+ [ key : string ] : any ;
97
+ }
98
+
99
+ /** Enums for multi-factor identifiers. */
100
+ export enum MultiFactorId {
101
+ Phone = 'phone' ,
102
+ }
103
+
104
+ /**
105
+ * Abstract class representing a multi-factor info interface.
106
+ */
107
+ export abstract class MultiFactorInfo {
108
+ public readonly uid : string ;
109
+ public readonly displayName : string | null ;
110
+ public readonly factorId : MultiFactorId ;
111
+ public readonly enrollmentTime : string ;
112
+
113
+ /**
114
+ * Initializes the MultiFactorInfo associated subclass using the server side.
115
+ * If no MultiFactorInfo is associated with the response, null is returned.
116
+ *
117
+ * @param response The server side response.
118
+ * @constructor
119
+ */
120
+ public static initMultiFactorInfo ( response : AuthFactorInfo ) : MultiFactorInfo | null {
121
+ let multiFactorInfo : MultiFactorInfo | null = null ;
122
+ // Only PhoneMultiFactorInfo currently available.
123
+ try {
124
+ multiFactorInfo = new PhoneMultiFactorInfo ( response ) ;
125
+ } catch ( e ) {
126
+ // Ignore error.
127
+ }
128
+ return multiFactorInfo ;
129
+ }
130
+
131
+ /**
132
+ * Initializes the MultiFactorInfo object using the server side response.
133
+ *
134
+ * @param response The server side response.
135
+ * @constructor
136
+ */
137
+ constructor ( response : AuthFactorInfo ) {
138
+ this . initFromServerResponse ( response ) ;
139
+ }
140
+
141
+ /** @return The plain object representation. */
142
+ public toJSON ( ) : any {
143
+ return {
144
+ uid : this . uid ,
145
+ displayName : this . displayName ,
146
+ factorId : this . factorId ,
147
+ enrollmentTime : this . enrollmentTime ,
148
+ } ;
149
+ }
150
+
151
+ /**
152
+ * Returns the factor ID based on the response provided.
153
+ *
154
+ * @param response The server side response.
155
+ * @return The multi-factor ID associated with the provided response. If the response is
156
+ * not associated with any known multi-factor ID, null is returned.
157
+ */
158
+ protected abstract getFactorId ( response : AuthFactorInfo ) : MultiFactorId | null ;
159
+
160
+ /**
161
+ * Initializes the MultiFactorInfo object using the provided server response.
162
+ *
163
+ * @param response The server side response.
164
+ */
165
+ private initFromServerResponse ( response : AuthFactorInfo ) {
166
+ const factorId = response && this . getFactorId ( response ) ;
167
+ if ( ! factorId || ! response || ! response . mfaEnrollmentId ) {
168
+ throw new FirebaseAuthError (
169
+ AuthClientErrorCode . INTERNAL_ERROR ,
170
+ 'INTERNAL ASSERT FAILED: Invalid multi-factor info response' ) ;
171
+ }
172
+ utils . addReadonlyGetter ( this , 'uid' , response . mfaEnrollmentId ) ;
173
+ utils . addReadonlyGetter ( this , 'factorId' , factorId ) ;
174
+ utils . addReadonlyGetter ( this , 'displayName' , response . displayName || null ) ;
175
+ // Encoded using [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format.
176
+ // For example, "2017-01-15T01:30:15.01Z".
177
+ // This can be parsed directly via Date constructor.
178
+ // This can be computed using Data.prototype.toISOString.
179
+ if ( response . enrolledAt ) {
180
+ utils . addReadonlyGetter (
181
+ this , 'enrollmentTime' , new Date ( response . enrolledAt ) . toUTCString ( ) ) ;
182
+ } else {
183
+ utils . addReadonlyGetter ( this , 'enrollmentTime' , null ) ;
184
+ }
185
+ }
186
+ }
187
+
188
+ /** Class representing a phone MultiFactorInfo object. */
189
+ export class PhoneMultiFactorInfo extends MultiFactorInfo {
190
+ public readonly phoneNumber : string ;
191
+
192
+ /**
193
+ * Initializes the PhoneMultiFactorInfo object using the server side response.
194
+ *
195
+ * @param response The server side response.
196
+ * @constructor
197
+ */
198
+ constructor ( response : AuthFactorInfo ) {
199
+ super ( response ) ;
200
+ utils . addReadonlyGetter ( this , 'phoneNumber' , response . phoneInfo ) ;
201
+ }
202
+
203
+ /** @return The plain object representation. */
204
+ public toJSON ( ) : any {
205
+ return Object . assign (
206
+ super . toJSON ( ) ,
207
+ {
208
+ phoneNumber : this . phoneNumber ,
209
+ } ) ;
210
+ }
211
+
212
+ /**
213
+ * Returns the factor ID based on the response provided.
214
+ *
215
+ * @param response The server side response.
216
+ * @return The multi-factor ID associated with the provided response. If the response is
217
+ * not associated with any known multi-factor ID, null is returned.
218
+ */
219
+ protected getFactorId ( response : AuthFactorInfo ) : MultiFactorId | null {
220
+ return ! ! ( response && response . phoneInfo ) ? MultiFactorId . Phone : null ;
221
+ }
222
+ }
223
+
224
+ /** Class representing multi-factor related properties of a user. */
225
+ export class MultiFactor {
226
+ public readonly enrolledFactors : ReadonlyArray < MultiFactorInfo > ;
227
+
228
+ /**
229
+ * Initializes the MultiFactor object using the server side or JWT format response.
230
+ *
231
+ * @param response The server side response.
232
+ * @constructor
233
+ */
234
+ constructor ( response : GetAccountInfoUserResponse ) {
235
+ const parsedEnrolledFactors : MultiFactorInfo [ ] = [ ] ;
236
+ if ( ! isNonNullObject ( response ) ) {
237
+ throw new FirebaseAuthError (
238
+ AuthClientErrorCode . INTERNAL_ERROR ,
239
+ 'INTERNAL ASSERT FAILED: Invalid multi-factor response' ) ;
240
+ } else if ( response . mfaInfo ) {
241
+ response . mfaInfo . forEach ( ( factorResponse ) => {
242
+ const multiFactorInfo = MultiFactorInfo . initMultiFactorInfo ( factorResponse ) ;
243
+ if ( multiFactorInfo ) {
244
+ parsedEnrolledFactors . push ( multiFactorInfo ) ;
245
+ }
246
+ } ) ;
247
+ }
248
+ // Make enrolled factors immutable.
249
+ utils . addReadonlyGetter (
250
+ this , 'enrolledFactors' , Object . freeze ( parsedEnrolledFactors ) ) ;
251
+ }
252
+
253
+ /** @return The plain object representation. */
254
+ public toJSON ( ) : any {
255
+ return {
256
+ enrolledFactors : this . enrolledFactors . map ( ( info ) => info . toJSON ( ) ) ,
257
+ } ;
258
+ }
259
+ }
260
+
60
261
/**
61
262
* User metadata class that provides metadata information like user account creation
62
263
* and last sign in time.
63
264
*
64
- * @param { object } response The server side response returned from the getAccountInfo
265
+ * @param response The server side response returned from the getAccountInfo
65
266
* endpoint.
66
267
* @constructor
67
268
*/
68
269
export class UserMetadata {
69
270
public readonly creationTime : string ;
70
271
public readonly lastSignInTime : string ;
71
272
72
- constructor ( response : any ) {
273
+ constructor ( response : GetAccountInfoUserResponse ) {
73
274
// Creation date should always be available but due to some backend bugs there
74
275
// were cases in the past where users did not have creation date properly set.
75
276
// This included legacy Firebase migrating project users and some anonymous users.
@@ -78,7 +279,7 @@ export class UserMetadata {
78
279
utils . addReadonlyGetter ( this , 'lastSignInTime' , parseDate ( response . lastLoginAt ) ) ;
79
280
}
80
281
81
- /** @return { object } The plain object representation of the user's metadata. */
282
+ /** @return The plain object representation of the user's metadata. */
82
283
public toJSON ( ) : object {
83
284
return {
84
285
lastSignInTime : this . lastSignInTime ,
@@ -91,7 +292,7 @@ export class UserMetadata {
91
292
* User info class that provides provider user information for different
92
293
* Firebase providers like google.com, facebook.com, password, etc.
93
294
*
94
- * @param { object } response The server side response returned from the getAccountInfo
295
+ * @param response The server side response returned from the getAccountInfo
95
296
* endpoint.
96
297
* @constructor
97
298
*/
@@ -103,7 +304,7 @@ export class UserInfo {
103
304
public readonly providerId : string ;
104
305
public readonly phoneNumber : string ;
105
306
106
- constructor ( response : any ) {
307
+ constructor ( response : ProviderUserInfo ) {
107
308
// Provider user id and provider id are required.
108
309
if ( ! response . rawId || ! response . providerId ) {
109
310
throw new FirebaseAuthError (
@@ -119,7 +320,7 @@ export class UserInfo {
119
320
utils . addReadonlyGetter ( this , 'phoneNumber' , response . phoneNumber ) ;
120
321
}
121
322
122
- /** @return { object } The plain object representation of the current provider data. */
323
+ /** @return The plain object representation of the current provider data. */
123
324
public toJSON ( ) : object {
124
325
return {
125
326
uid : this . uid ,
@@ -136,7 +337,7 @@ export class UserInfo {
136
337
* User record class that defines the Firebase user object populated from
137
338
* the Firebase Auth getAccountInfo response.
138
339
*
139
- * @param { any } response The server side response returned from the getAccountInfo
340
+ * @param response The server side response returned from the getAccountInfo
140
341
* endpoint.
141
342
* @constructor
142
343
*/
@@ -155,8 +356,9 @@ export class UserRecord {
155
356
public readonly customClaims : object ;
156
357
public readonly tenantId ?: string | null ;
157
358
public readonly tokensValidAfterTime ?: string ;
359
+ public readonly multiFactor ?: MultiFactor ;
158
360
159
- constructor ( response : any ) {
361
+ constructor ( response : GetAccountInfoUserResponse ) {
160
362
// The Firebase user id is required.
161
363
if ( ! response . localId ) {
162
364
throw new FirebaseAuthError (
@@ -199,13 +401,17 @@ export class UserRecord {
199
401
let validAfterTime : string = null ;
200
402
// Convert validSince first to UTC milliseconds and then to UTC date string.
201
403
if ( typeof response . validSince !== 'undefined' ) {
202
- validAfterTime = parseDate ( response . validSince * 1000 ) ;
404
+ validAfterTime = parseDate ( parseInt ( response . validSince , 10 ) * 1000 ) ;
203
405
}
204
406
utils . addReadonlyGetter ( this , 'tokensValidAfterTime' , validAfterTime || undefined ) ;
205
407
utils . addReadonlyGetter ( this , 'tenantId' , response . tenantId ) ;
408
+ const multiFactor = new MultiFactor ( response ) ;
409
+ if ( multiFactor . enrolledFactors . length > 0 ) {
410
+ utils . addReadonlyGetter ( this , 'multiFactor' , multiFactor ) ;
411
+ }
206
412
}
207
413
208
- /** @return { object } The plain object representation of the user record. */
414
+ /** @return The plain object representation of the user record. */
209
415
public toJSON ( ) : object {
210
416
const json : any = {
211
417
uid : this . uid ,
@@ -223,6 +429,9 @@ export class UserRecord {
223
429
tokensValidAfterTime : this . tokensValidAfterTime ,
224
430
tenantId : this . tenantId ,
225
431
} ;
432
+ if ( this . multiFactor ) {
433
+ json . multiFactor = this . multiFactor . toJSON ( ) ;
434
+ }
226
435
json . providerData = [ ] ;
227
436
for ( const entry of this . providerData ) {
228
437
// Convert each provider data to json.
0 commit comments