@@ -2,14 +2,38 @@ import { ApiSettings } from './api-requests';
22import { BaseClient } from './client' ;
33import type { EmulatorEnv } from './emulator' ;
44import { AuthClientErrorCode , FirebaseAuthError } from './errors' ;
5- import { isNonEmptyString , isNumber } from './validator' ;
5+ import { UserRecord } from './user-record' ;
6+ import { isNonEmptyString , isNumber , isObject , isUid } from './validator' ;
67
78/** Minimum allowed session cookie duration in seconds (5 minutes). */
89const MIN_SESSION_COOKIE_DURATION_SECS = 5 * 60 ;
910
1011/** Maximum allowed session cookie duration in seconds (2 weeks). */
1112const MAX_SESSION_COOKIE_DURATION_SECS = 14 * 24 * 60 * 60 ;
1213
14+ /** List of reserved claims which cannot be provided when creating a custom token. */
15+ const RESERVED_CLAIMS = [
16+ 'acr' ,
17+ 'amr' ,
18+ 'at_hash' ,
19+ 'aud' ,
20+ 'auth_time' ,
21+ 'azp' ,
22+ 'cnf' ,
23+ 'c_hash' ,
24+ 'exp' ,
25+ 'iat' ,
26+ 'iss' ,
27+ 'jti' ,
28+ 'nbf' ,
29+ 'nonce' ,
30+ 'sub' ,
31+ 'firebase' ,
32+ ] ;
33+
34+ /** Maximum allowed number of characters in the custom claims payload. */
35+ const MAX_CLAIMS_PAYLOAD_SIZE = 1000 ;
36+
1337/**
1438 * Instantiates the createSessionCookie endpoint settings.
1539 *
@@ -39,6 +63,131 @@ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = new ApiSettings('v1', ':creat
3963 }
4064 } ) ;
4165
66+ interface GetAccountInfoRequest {
67+ localId ?: string [ ] ;
68+ email ?: string [ ] ;
69+ phoneNumber ?: string [ ] ;
70+ federatedUserId ?: Array < {
71+ providerId : string ;
72+ rawId : string ;
73+ } > ;
74+ }
75+
76+ /**
77+ * Instantiates the getAccountInfo endpoint settings.
78+ *
79+ * @internal
80+ */
81+ export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings ( 'v1' , '/accounts:lookup' , 'POST' )
82+ // Set request validator.
83+ . setRequestValidator ( ( request : GetAccountInfoRequest ) => {
84+ if ( ! request . localId && ! request . email && ! request . phoneNumber && ! request . federatedUserId ) {
85+ throw new FirebaseAuthError (
86+ AuthClientErrorCode . INTERNAL_ERROR ,
87+ 'INTERNAL ASSERT FAILED: Server request is missing user identifier'
88+ ) ;
89+ }
90+ } )
91+ // Set response validator.
92+ . setResponseValidator ( ( response : any ) => {
93+ if ( ! response . users || ! response . users . length ) {
94+ throw new FirebaseAuthError ( AuthClientErrorCode . USER_NOT_FOUND ) ;
95+ }
96+ } ) ;
97+
98+ /**
99+ * Instantiates the revokeRefreshTokens endpoint settings for updating existing accounts.
100+ *
101+ * @internal
102+ * @link https://github.com/firebase/firebase-admin-node/blob/9955bca47249301aa970679ae99fe01d54adf6a8/src/auth/auth-api-request.ts#L746
103+ */
104+ export const FIREBASE_AUTH_REVOKE_REFRESH_TOKENS = new ApiSettings ( 'v1' , '/accounts:update' , 'POST' )
105+ // Set request validator.
106+ . setRequestValidator ( ( request : any ) => {
107+ // localId is a required parameter.
108+ if ( typeof request . localId === 'undefined' ) {
109+ throw new FirebaseAuthError (
110+ AuthClientErrorCode . INTERNAL_ERROR ,
111+ 'INTERNAL ASSERT FAILED: Server request is missing user identifier'
112+ ) ;
113+ }
114+ // validSince should be a number.
115+ if ( typeof request . validSince !== 'undefined' && ! isNumber ( request . validSince ) ) {
116+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_TOKENS_VALID_AFTER_TIME ) ;
117+ }
118+ } )
119+ // Set response validator.
120+ . setResponseValidator ( ( response : any ) => {
121+ // If the localId is not returned, then the request failed.
122+ if ( ! response . localId ) {
123+ throw new FirebaseAuthError ( AuthClientErrorCode . USER_NOT_FOUND ) ;
124+ }
125+ } ) ;
126+
127+ /**
128+ * Instantiates the setCustomUserClaims endpoint settings for updating existing accounts.
129+ *
130+ * @internal
131+ * @link https://github.com/firebase/firebase-admin-node/blob/9955bca47249301aa970679ae99fe01d54adf6a8/src/auth/auth-api-request.ts#L746
132+ */
133+ export const FIREBASE_AUTH_SET_CUSTOM_USER_CLAIMS = new ApiSettings ( 'v1' , '/accounts:update' , 'POST' )
134+ // Set request validator.
135+ . setRequestValidator ( ( request : any ) => {
136+ // localId is a required parameter.
137+ if ( typeof request . localId === 'undefined' ) {
138+ throw new FirebaseAuthError (
139+ AuthClientErrorCode . INTERNAL_ERROR ,
140+ 'INTERNAL ASSERT FAILED: Server request is missing user identifier'
141+ ) ;
142+ }
143+ // customAttributes should be stringified JSON with no blacklisted claims.
144+ // The payload should not exceed 1KB.
145+ if ( typeof request . customAttributes !== 'undefined' ) {
146+ let developerClaims : object ;
147+ try {
148+ developerClaims = JSON . parse ( request . customAttributes ) ;
149+ } catch ( error ) {
150+ if ( error instanceof Error ) {
151+ // JSON parsing error. This should never happen as we stringify the claims internally.
152+ // However, we still need to check since setAccountInfo via edit requests could pass
153+ // this field.
154+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_CLAIMS , error . message ) ;
155+ }
156+ throw error ;
157+ }
158+ const invalidClaims : string [ ] = [ ] ;
159+ // Check for any invalid claims.
160+ RESERVED_CLAIMS . forEach ( blacklistedClaim => {
161+ if ( Object . prototype . hasOwnProperty . call ( developerClaims , blacklistedClaim ) ) {
162+ invalidClaims . push ( blacklistedClaim ) ;
163+ }
164+ } ) ;
165+ // Throw an error if an invalid claim is detected.
166+ if ( invalidClaims . length > 0 ) {
167+ throw new FirebaseAuthError (
168+ AuthClientErrorCode . FORBIDDEN_CLAIM ,
169+ invalidClaims . length > 1
170+ ? `Developer claims "${ invalidClaims . join ( '", "' ) } " are reserved and cannot be specified.`
171+ : `Developer claim "${ invalidClaims [ 0 ] } " is reserved and cannot be specified.`
172+ ) ;
173+ }
174+ // Check claims payload does not exceed maxmimum size.
175+ if ( request . customAttributes . length > MAX_CLAIMS_PAYLOAD_SIZE ) {
176+ throw new FirebaseAuthError (
177+ AuthClientErrorCode . CLAIMS_TOO_LARGE ,
178+ `Developer claims payload should not exceed ${ MAX_CLAIMS_PAYLOAD_SIZE } characters.`
179+ ) ;
180+ }
181+ }
182+ } )
183+ // Set response validator.
184+ . setResponseValidator ( ( response : any ) => {
185+ // If the localId is not returned, then the request failed.
186+ if ( ! response . localId ) {
187+ throw new FirebaseAuthError ( AuthClientErrorCode . USER_NOT_FOUND ) ;
188+ }
189+ } ) ;
190+
42191export class AuthApiClient extends BaseClient {
43192 /**
44193 * Creates a new Firebase session cookie with the specified duration that can be used for
@@ -47,7 +196,7 @@ export class AuthApiClient extends BaseClient {
47196 *
48197 * @param idToken - The Firebase ID token to exchange for a session cookie.
49198 * @param expiresIn - The session cookie duration in milliseconds.
50- * @param - An optional parameter specifying the environment in which the function is running.
199+ * @param env - An optional parameter specifying the environment in which the function is running.
51200 * If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
52201 * If not specified, the function will assume it is running in a production environment.
53202 *
@@ -62,4 +211,90 @@ export class AuthApiClient extends BaseClient {
62211 const res = await this . fetch < { sessionCookie : string } > ( FIREBASE_AUTH_CREATE_SESSION_COOKIE , request , env ) ;
63212 return res . sessionCookie ;
64213 }
214+
215+ /**
216+ * Looks up a user by uid.
217+ *
218+ * @param uid - The uid of the user to lookup.
219+ * @param env - An optional parameter specifying the environment in which the function is running.
220+ * If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
221+ * If not specified, the function will assume it is running in a production environment.
222+ * @returns A promise that resolves with the user information.
223+ */
224+ public async getAccountInfoByUid ( uid : string , env ?: EmulatorEnv ) : Promise < UserRecord > {
225+ if ( ! isUid ( uid ) ) {
226+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_UID ) ;
227+ }
228+
229+ const request = {
230+ localId : [ uid ] ,
231+ } ;
232+ const res = await this . fetch < object > ( FIREBASE_AUTH_GET_ACCOUNT_INFO , request , env ) ;
233+ // Returns the user record populated with server response.
234+ return new UserRecord ( ( res as any ) . users [ 0 ] ) ;
235+ }
236+
237+ /**
238+ * Revokes all refresh tokens for the specified user identified by the uid provided.
239+ * In addition to revoking all refresh tokens for a user, all ID tokens issued
240+ * before revocation will also be revoked on the Auth backend. Any request with an
241+ * ID token generated before revocation will be rejected with a token expired error.
242+ * Note that due to the fact that the timestamp is stored in seconds, any tokens minted in
243+ * the same second as the revocation will still be valid. If there is a chance that a token
244+ * was minted in the last second, delay for 1 second before revoking.
245+ *
246+ * @param uid - The user whose tokens are to be revoked.
247+ * @param env - An optional parameter specifying the environment in which the function is running.
248+ * If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
249+ * If not specified, the function will assume it is running in a production environment.
250+ * @returns A promise that resolves when the operation completes
251+ * successfully with the user id of the corresponding user.
252+ */
253+ public async revokeRefreshTokens ( uid : string , env ?: EmulatorEnv ) : Promise < string > {
254+ // Validate user UID.
255+ if ( ! isUid ( uid ) ) {
256+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_UID ) ;
257+ }
258+ const request : any = {
259+ localId : uid ,
260+ // validSince is in UTC seconds.
261+ validSince : Math . floor ( new Date ( ) . getTime ( ) / 1000 ) ,
262+ } ;
263+ const res = await this . fetch < { localId : string } > ( FIREBASE_AUTH_REVOKE_REFRESH_TOKENS , request , env ) ;
264+ return res . localId ;
265+ }
266+
267+ /**
268+ * Sets additional developer claims on an existing user identified by provided UID.
269+ *
270+ * @param uid - The user to edit.
271+ * @param customUserClaims - The developer claims to set.
272+ * @param env - An optional parameter specifying the environment in which the function is running.
273+ * If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
274+ * If not specified, the function will assume it is running in a production environment.
275+ * @returns A promise that resolves when the operation completes
276+ * with the user id that was edited.
277+ */
278+ public async setCustomUserClaims ( uid : string , customUserClaims : object | null , env ?: EmulatorEnv ) : Promise < string > {
279+ // Validate user UID.
280+ if ( ! isUid ( uid ) ) {
281+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_UID ) ;
282+ } else if ( ! isObject ( customUserClaims ) ) {
283+ throw new FirebaseAuthError (
284+ AuthClientErrorCode . INVALID_ARGUMENT ,
285+ 'CustomUserClaims argument must be an object or null.'
286+ ) ;
287+ }
288+ // Delete operation. Replace null with an empty object.
289+ if ( customUserClaims === null ) {
290+ customUserClaims = { } ;
291+ }
292+ // Construct custom user attribute editting request.
293+ const request : any = {
294+ localId : uid ,
295+ customAttributes : JSON . stringify ( customUserClaims ) ,
296+ } ;
297+ const res = await this . fetch < { localId : string } > ( FIREBASE_AUTH_SET_CUSTOM_USER_CLAIMS , request , env ) ;
298+ return res . localId ;
299+ }
65300}
0 commit comments