@@ -2,14 +2,38 @@ import { ApiSettings } from './api-requests';
2
2
import { BaseClient } from './client' ;
3
3
import type { EmulatorEnv } from './emulator' ;
4
4
import { AuthClientErrorCode , FirebaseAuthError } from './errors' ;
5
- import { isNonEmptyString , isNumber } from './validator' ;
5
+ import { UserRecord } from './user-record' ;
6
+ import { isNonEmptyString , isNumber , isObject , isUid } from './validator' ;
6
7
7
8
/** Minimum allowed session cookie duration in seconds (5 minutes). */
8
9
const MIN_SESSION_COOKIE_DURATION_SECS = 5 * 60 ;
9
10
10
11
/** Maximum allowed session cookie duration in seconds (2 weeks). */
11
12
const MAX_SESSION_COOKIE_DURATION_SECS = 14 * 24 * 60 * 60 ;
12
13
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
+
13
37
/**
14
38
* Instantiates the createSessionCookie endpoint settings.
15
39
*
@@ -39,6 +63,131 @@ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = new ApiSettings('v1', ':creat
39
63
}
40
64
} ) ;
41
65
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
+
42
191
export class AuthApiClient extends BaseClient {
43
192
/**
44
193
* Creates a new Firebase session cookie with the specified duration that can be used for
@@ -47,7 +196,7 @@ export class AuthApiClient extends BaseClient {
47
196
*
48
197
* @param idToken - The Firebase ID token to exchange for a session cookie.
49
198
* @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.
51
200
* If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
52
201
* If not specified, the function will assume it is running in a production environment.
53
202
*
@@ -62,4 +211,90 @@ export class AuthApiClient extends BaseClient {
62
211
const res = await this . fetch < { sessionCookie : string } > ( FIREBASE_AUTH_CREATE_SESSION_COOKIE , request , env ) ;
63
212
return res . sessionCookie ;
64
213
}
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
+ }
65
300
}
0 commit comments