@@ -41,6 +41,19 @@ const FIREBASE_AUTH_HEADER = {
41
41
const FIREBASE_AUTH_TIMEOUT = 10000 ;
42
42
43
43
44
+ /** List of reserved claims which cannot be provided when creating a custom token. */
45
+ export const RESERVED_CLAIMS = [
46
+ 'acr' , 'amr' , 'at_hash' , 'aud' , 'auth_time' , 'azp' , 'cnf' , 'c_hash' , 'exp' , 'iat' ,
47
+ 'iss' , 'jti' , 'nbf' , 'nonce' , 'sub' , 'firebase' ,
48
+ ] ;
49
+
50
+ /** Maximum allowed number of characters in the custom claims payload. */
51
+ const MAX_CLAIMS_PAYLOAD_SIZE = 1000 ;
52
+
53
+ /** Maximum allowed number of users to batch download at one time. */
54
+ const MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE = 1000 ;
55
+
56
+
44
57
/**
45
58
* Validates a create/edit request object. All unsupported parameters
46
59
* are removed from the original request. If an invalid field is passed
@@ -64,6 +77,7 @@ function validateCreateEditRequest(request: any) {
64
77
deleteProvider : true ,
65
78
sanityCheck : true ,
66
79
phoneNumber : true ,
80
+ customAttributes : true ,
67
81
} ;
68
82
// Remove invalid keys from original request.
69
83
for ( let key in request ) {
@@ -127,9 +141,67 @@ function validateCreateEditRequest(request: any) {
127
141
// disabled externally. So the error message should use the client facing name.
128
142
throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_DISABLED_FIELD ) ;
129
143
}
144
+ // customAttributes should be stringified JSON with no blacklisted claims.
145
+ // The payload should not exceed 1KB.
146
+ if ( typeof request . customAttributes !== 'undefined' ) {
147
+ let developerClaims ;
148
+ try {
149
+ developerClaims = JSON . parse ( request . customAttributes ) ;
150
+ } catch ( 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
+ const invalidClaims = [ ] ;
157
+ // Check for any invalid claims.
158
+ RESERVED_CLAIMS . forEach ( ( blacklistedClaim ) => {
159
+ if ( developerClaims . hasOwnProperty ( blacklistedClaim ) ) {
160
+ invalidClaims . push ( blacklistedClaim ) ;
161
+ }
162
+ } ) ;
163
+ // Throw an error if an invalid claim is detected.
164
+ if ( invalidClaims . length > 0 ) {
165
+ throw new FirebaseAuthError (
166
+ AuthClientErrorCode . FORBIDDEN_CLAIM ,
167
+ invalidClaims . length > 1 ?
168
+ `Developer claims "${ invalidClaims . join ( '", "' ) } " are reserved and cannot be specified.` :
169
+ `Developer claim "${ invalidClaims [ 0 ] } " is reserved and cannot be specified.` ,
170
+ ) ;
171
+ }
172
+ // Check claims payload does not exceed maxmimum size.
173
+ if ( request . customAttributes . length > MAX_CLAIMS_PAYLOAD_SIZE ) {
174
+ throw new FirebaseAuthError (
175
+ AuthClientErrorCode . CLAIMS_TOO_LARGE ,
176
+ `Developer claims payload should not exceed ${ MAX_CLAIMS_PAYLOAD_SIZE } characters.` ,
177
+ ) ;
178
+ }
179
+ }
130
180
} ;
131
181
132
182
183
+ /** Instantiates the downloadAccount endpoint settings. */
184
+ export const FIREBASE_AUTH_DOWNLOAD_ACCOUNT = new ApiSettings ( 'downloadAccount' , 'POST' )
185
+ // Set request validator.
186
+ . setRequestValidator ( ( request : any ) => {
187
+ // Validate next page token.
188
+ if ( typeof request . nextPageToken !== 'undefined' &&
189
+ ! validator . isNonEmptyString ( request . nextPageToken ) ) {
190
+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_PAGE_TOKEN ) ;
191
+ }
192
+ // Validate max results.
193
+ if ( ! validator . isNumber ( request . maxResults ) ||
194
+ request . maxResults <= 0 ||
195
+ request . maxResults > MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE ) {
196
+ throw new FirebaseAuthError (
197
+ AuthClientErrorCode . INVALID_ARGUMENT ,
198
+ `Required "maxResults" must be a positive non-zero number that does not exceed ` +
199
+ `the allowed ${ MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE } .`
200
+ ) ;
201
+ }
202
+ } ) ;
203
+
204
+
133
205
/** Instantiates the getAccountInfo endpoint settings. */
134
206
export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings ( 'getAccountInfo' , 'POST' )
135
207
// Set request validator.
@@ -185,6 +257,13 @@ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('setAccountInfo',
185
257
export const FIREBASE_AUTH_SIGN_UP_NEW_USER = new ApiSettings ( 'signupNewUser' , 'POST' )
186
258
// Set request validator.
187
259
. setRequestValidator ( ( request : any ) => {
260
+ // signupNewUser does not support customAttributes.
261
+ if ( typeof request . customAttributes !== 'undefined' ) {
262
+ throw new FirebaseAuthError (
263
+ AuthClientErrorCode . INVALID_ARGUMENT ,
264
+ `"customAttributes" cannot be set when creating a new user.` ,
265
+ ) ;
266
+ }
188
267
validateCreateEditRequest ( request ) ;
189
268
} )
190
269
// Set response validator.
@@ -275,6 +354,40 @@ export class FirebaseAuthRequestHandler {
275
354
return this . invokeRequestHandler ( FIREBASE_AUTH_GET_ACCOUNT_INFO , request ) ;
276
355
}
277
356
357
+ /**
358
+ * Exports the users (single batch only) with a size of maxResults and starting from
359
+ * the offset as specified by pageToken.
360
+ *
361
+ * @param {number= } maxResults The page size, 1000 if undefined. This is also the maximum
362
+ * allowed limit.
363
+ * @param {string= } pageToken The next page token. If not specified, returns users starting
364
+ * without any offset. Users are returned in the order they were created from oldest to
365
+ * newest, relative to the page token offset.
366
+ * @return {Promise<Object> } A promise that resolves with the current batch of downloaded
367
+ * users and the next page token if available. For the last page, an empty list of users
368
+ * and no page token are returned.
369
+ */
370
+ public downloadAccount (
371
+ maxResults : number = MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE ,
372
+ pageToken ?: string ) : Promise < { users : Object [ ] , nextPageToken ?: string } > {
373
+ // Construct request.
374
+ const request = {
375
+ maxResults,
376
+ nextPageToken : pageToken ,
377
+ } ;
378
+ // Remove next page token if not provided.
379
+ if ( typeof request . nextPageToken === 'undefined' ) {
380
+ delete request . nextPageToken ;
381
+ }
382
+ return this . invokeRequestHandler ( FIREBASE_AUTH_DOWNLOAD_ACCOUNT , request )
383
+ . then ( ( response : any ) => {
384
+ // No more users available.
385
+ if ( ! response . users ) {
386
+ response . users = [ ] ;
387
+ }
388
+ return response as { users : Object [ ] , nextPageToken ?: string } ;
389
+ } ) ;
390
+ }
278
391
279
392
/**
280
393
* Deletes an account identified by a uid.
@@ -293,6 +406,41 @@ export class FirebaseAuthRequestHandler {
293
406
return this . invokeRequestHandler ( FIREBASE_AUTH_DELETE_ACCOUNT , request ) ;
294
407
}
295
408
409
+ /**
410
+ * Sets additional developer claims on an existing user identified by provided UID.
411
+ *
412
+ * @param {string } uid The user to edit.
413
+ * @param {Object } customUserClaims The developer claims to set.
414
+ * @return {Promise<string> } A promise that resolves when the operation completes
415
+ * with the user id that was edited.
416
+ */
417
+ public setCustomUserClaims ( uid : string , customUserClaims : Object ) : Promise < string > {
418
+ // Validate user UID.
419
+ if ( ! validator . isUid ( uid ) ) {
420
+ return Promise . reject ( new FirebaseAuthError ( AuthClientErrorCode . INVALID_UID ) ) ;
421
+ } else if ( ! validator . isObject ( customUserClaims ) ) {
422
+ return Promise . reject (
423
+ new FirebaseAuthError (
424
+ AuthClientErrorCode . INVALID_ARGUMENT ,
425
+ 'CustomUserClaims argument must be an object or null.' ,
426
+ ) ,
427
+ ) ;
428
+ }
429
+ // Delete operation. Replace null with an empty object.
430
+ if ( customUserClaims === null ) {
431
+ customUserClaims = { } ;
432
+ }
433
+ // Construct custom user attribute editting request.
434
+ let request : any = {
435
+ localId : uid ,
436
+ customAttributes : JSON . stringify ( customUserClaims ) ,
437
+ } ;
438
+ return this . invokeRequestHandler ( FIREBASE_AUTH_SET_ACCOUNT_INFO , request )
439
+ . then ( ( response : any ) => {
440
+ return response . localId as string ;
441
+ } ) ;
442
+ }
443
+
296
444
/**
297
445
* Edits an existing user.
298
446
*
0 commit comments