1515 */
1616
1717import { deepCopy } from '../utils/deep-copy' ;
18+ import { isNonNullObject } from '../utils/validator' ;
1819import * as utils from '../utils' ;
1920import { AuthClientErrorCode , FirebaseAuthError } from '../utils/error' ;
2021
@@ -26,8 +27,8 @@ const B64_REDACTED = Buffer.from('REDACTED').toString('base64');
2627/**
2728 * Parses a time stamp string or number and returns the corresponding date if valid.
2829 *
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.
3132 */
3233function parseDate ( time : any ) : string {
3334 try {
@@ -57,19 +58,219 @@ export interface CreateRequest extends UpdateRequest {
5758 uid ?: string ;
5859}
5960
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+
60261/**
61262 * User metadata class that provides metadata information like user account creation
62263 * and last sign in time.
63264 *
64- * @param { object } response The server side response returned from the getAccountInfo
265+ * @param response The server side response returned from the getAccountInfo
65266 * endpoint.
66267 * @constructor
67268 */
68269export class UserMetadata {
69270 public readonly creationTime : string ;
70271 public readonly lastSignInTime : string ;
71272
72- constructor ( response : any ) {
273+ constructor ( response : GetAccountInfoUserResponse ) {
73274 // Creation date should always be available but due to some backend bugs there
74275 // were cases in the past where users did not have creation date properly set.
75276 // This included legacy Firebase migrating project users and some anonymous users.
@@ -78,7 +279,7 @@ export class UserMetadata {
78279 utils . addReadonlyGetter ( this , 'lastSignInTime' , parseDate ( response . lastLoginAt ) ) ;
79280 }
80281
81- /** @return { object } The plain object representation of the user's metadata. */
282+ /** @return The plain object representation of the user's metadata. */
82283 public toJSON ( ) : object {
83284 return {
84285 lastSignInTime : this . lastSignInTime ,
@@ -91,7 +292,7 @@ export class UserMetadata {
91292 * User info class that provides provider user information for different
92293 * Firebase providers like google.com, facebook.com, password, etc.
93294 *
94- * @param { object } response The server side response returned from the getAccountInfo
295+ * @param response The server side response returned from the getAccountInfo
95296 * endpoint.
96297 * @constructor
97298 */
@@ -103,7 +304,7 @@ export class UserInfo {
103304 public readonly providerId : string ;
104305 public readonly phoneNumber : string ;
105306
106- constructor ( response : any ) {
307+ constructor ( response : ProviderUserInfo ) {
107308 // Provider user id and provider id are required.
108309 if ( ! response . rawId || ! response . providerId ) {
109310 throw new FirebaseAuthError (
@@ -119,7 +320,7 @@ export class UserInfo {
119320 utils . addReadonlyGetter ( this , 'phoneNumber' , response . phoneNumber ) ;
120321 }
121322
122- /** @return { object } The plain object representation of the current provider data. */
323+ /** @return The plain object representation of the current provider data. */
123324 public toJSON ( ) : object {
124325 return {
125326 uid : this . uid ,
@@ -136,7 +337,7 @@ export class UserInfo {
136337 * User record class that defines the Firebase user object populated from
137338 * the Firebase Auth getAccountInfo response.
138339 *
139- * @param { any } response The server side response returned from the getAccountInfo
340+ * @param response The server side response returned from the getAccountInfo
140341 * endpoint.
141342 * @constructor
142343 */
@@ -155,8 +356,9 @@ export class UserRecord {
155356 public readonly customClaims : object ;
156357 public readonly tenantId ?: string | null ;
157358 public readonly tokensValidAfterTime ?: string ;
359+ public readonly multiFactor ?: MultiFactor ;
158360
159- constructor ( response : any ) {
361+ constructor ( response : GetAccountInfoUserResponse ) {
160362 // The Firebase user id is required.
161363 if ( ! response . localId ) {
162364 throw new FirebaseAuthError (
@@ -199,13 +401,17 @@ export class UserRecord {
199401 let validAfterTime : string = null ;
200402 // Convert validSince first to UTC milliseconds and then to UTC date string.
201403 if ( typeof response . validSince !== 'undefined' ) {
202- validAfterTime = parseDate ( response . validSince * 1000 ) ;
404+ validAfterTime = parseDate ( parseInt ( response . validSince , 10 ) * 1000 ) ;
203405 }
204406 utils . addReadonlyGetter ( this , 'tokensValidAfterTime' , validAfterTime || undefined ) ;
205407 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+ }
206412 }
207413
208- /** @return { object } The plain object representation of the user record. */
414+ /** @return The plain object representation of the user record. */
209415 public toJSON ( ) : object {
210416 const json : any = {
211417 uid : this . uid ,
@@ -223,6 +429,9 @@ export class UserRecord {
223429 tokensValidAfterTime : this . tokensValidAfterTime ,
224430 tenantId : this . tenantId ,
225431 } ;
432+ if ( this . multiFactor ) {
433+ json . multiFactor = this . multiFactor . toJSON ( ) ;
434+ }
226435 json . providerData = [ ] ;
227436 for ( const entry of this . providerData ) {
228437 // Convert each provider data to json.
0 commit comments