-
Notifications
You must be signed in to change notification settings - Fork 390
feat(auth): Add bulk get/delete methods #726
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Avoids 'already-exists' errors. Note that these errors would occur not just for already existing uids, but also for email and phoneNumber fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks mostly good. Just a few suggestions and questions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Thanks @rsgowman
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for putting this together. Working my way through it.
.setRequestValidator((request: any) => { | ||
if (!request.localId && !request.email && !request.phoneNumber) { | ||
.setRequestValidator((request: GetAccountInfoRequest) => { | ||
if (!request.localId && !request.email && !request.phoneNumber && !request.federatedUserId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you validate the federatedUserId
content?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only that it's a non-empty string. (I'm not aware of a way to do any better...)
* Instantiates the getAccountInfo endpoint settings for use when fetching info | ||
* for multiple accounts. | ||
*/ | ||
export const FIREBASE_AUTH_GET_ACCOUNTS_INFO = new ApiSettings('/accounts:lookup', 'POST') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to duplicate FIREBASE_AUTH_GET_ACCOUNT_INFO
above? It is the same underlying API with similar request /response format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response validator differs. For singular lookups, a usernotfound error is raised if the user cannot be found. For bulk lookups, the return value is handled differently, so we don't want to throw this error.
An alternative would be to raise 'usernotfound' in all the calling locations rather than the response validator. This would make the FIREBASE_AUTH_GET_ACCOUNT[S]_INFO consistent (and match better with the actual backend api) but would involve duplicating the error handling code to a number of locations. (Which I don't object to. This file feels like the backend api, so removing things from it that adapt it to our client facing api seems to be a reasonable thing to do. The duplication could be minimized by using a helper.)
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think this is fine.
|
||
export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:batchDelete', 'POST') | ||
.setRequestValidator((request: BatchDeleteAccountsRequest) => { | ||
if (!request.localIds) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't you also validate the force
field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shield generators back online.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
src/auth/auth-api-request.ts
Outdated
.setResponseValidator((response: BatchDeleteAccountsResponse) => { | ||
const errors = response.errors || []; | ||
errors.forEach((batchDeleteErrorInfo) => { | ||
if (batchDeleteErrorInfo.index === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: typeof batchDeleteErrorInfo.index === 'undefined'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/index.d.ts
Outdated
* @param uids The `uids` corresponding to the users to delete. | ||
* | ||
* @return A Promise that resolves to the total number of successful/failed | ||
* deletions, as well as the array of errors that correspond to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
corresponds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
private static addProviderToRequest(id: ProviderIdentifier, request: GetAccountInfoRequest): GetAccountInfoRequest { | ||
if (!validator.isNonEmptyString(id.providerUid) || !validator.isNonEmptyString(id.providerId)) { | ||
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think invalid provider ID is different that invalid provider uid. The error is confusing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/auth/auth-api-request.ts
Outdated
public getAccountInfoByIdentifiers(identifiers: UserIdentifier[]): Promise<object> { | ||
if (identifiers.length === 0) { | ||
return Promise.resolve({users: []}); | ||
} else if (identifiers.length > 100) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe define 100 as a constant like we do for other similar max fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Also done for bulk delete (which was mistakenly set to 100 instead of 1000!)
src/auth/auth-api-request.ts
Outdated
public deleteAccounts(uids: string[], force: boolean): Promise<BatchDeleteAccountsResponse> { | ||
if (uids.length === 0) { | ||
return Promise.resolve({}); | ||
} else if (uids.length > 100) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here regarding defining a const for this upper limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/auth/auth.ts
Outdated
* Checks if the specified identifier is within the list of | ||
* UserRecords. | ||
*/ | ||
const isUserFound = ((id: UserIdentifier, urs: UserRecord[]): boolean => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does urs
stand for? Maybe pick a better name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
(urs => userRecords, ur => userRecord)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good. Just a couple of nits on documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a nit 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left small suggestion in addition to egilmore@'s comments. Otherwise LGTM.
(Covering on behalf of egilmore@)
test/integration/auth.spec.ts
Outdated
// New users should not have a lastRefreshTime set. | ||
expect(newUserRecord.metadata.lastRefreshTime).to.be.null; | ||
|
||
// Login to cause the lastRefreshTime to be set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest:
"// Log in to set the lastRefreshTime."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional minor edits
src/index.d.ts
Outdated
*/ | ||
creationTime: string; | ||
|
||
/** | ||
* The time at which the user was last active (ID token refreshed), or null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"The time at which the user was last active (ID token refreshed), formatted as a UTC Date string (e.g., 'Sat, 03 Feb 2001 04:05:06 GMT'). Returns null if the user was never active."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/index.d.ts
Outdated
/** | ||
* Deletes the users specified by the given uids. | ||
* | ||
* Deleting a non-existing user won't generate an error. (i.e. this method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleting a non-existing user won't generate an error (i.e. this method is idempotent).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good. Only thing is that I don't see any tests for auth-api-request.ts
src/auth/auth-api-request.ts
Outdated
/** Maximum allowed number of users to batch get at one time. */ | ||
const MAX_GET_ACCOUNTS_BATCH_SIZE = 100; | ||
|
||
/** Maximum allowed numberof users to batch delete at one time. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
number of
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:batchDelete', 'POST') | ||
.setRequestValidator((request: BatchDeleteAccountsRequest) => { | ||
if (!request.localIds) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
* Instantiates the getAccountInfo endpoint settings for use when fetching info | ||
* for multiple accounts. | ||
*/ | ||
export const FIREBASE_AUTH_GET_ACCOUNTS_INFO = new ApiSettings('/accounts:lookup', 'POST') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think this is fine.
src/auth/identifier.ts
Outdated
@@ -0,0 +1,78 @@ | |||
/*! | |||
* Copyright 2019 Google Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2020
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/index.d.ts
Outdated
@@ -604,7 +604,7 @@ declare namespace admin.credential { | |||
*/ | |||
interface Credential { | |||
|
|||
/** | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment alignment seem to be off here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. (merge conflict artifact)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (incl auth-api-request.ts tests)
src/auth/auth-api-request.ts
Outdated
/** Maximum allowed number of users to batch get at one time. */ | ||
const MAX_GET_ACCOUNTS_BATCH_SIZE = 100; | ||
|
||
/** Maximum allowed numberof users to batch delete at one time. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/auth/identifier.ts
Outdated
@@ -0,0 +1,78 @@ | |||
/*! | |||
* Copyright 2019 Google Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/index.d.ts
Outdated
@@ -604,7 +604,7 @@ declare namespace admin.credential { | |||
*/ | |||
interface Credential { | |||
|
|||
/** | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. (merge conflict artifact)
Granted coverage is still adequate but the testing format does not align with the existing pattern used here. Personally I prefer sticking with the current pattern. The pattern has been to test the RPC logic in auth-api-request.spec.ts |
Mostly done. I've moved some tests over entirely (where possible), kept some tests in auth.spec.ts (when those tests test behaviour entirely confined to auth.ts) and split some between the two (as auth.ts modifies the results from auth-api-request.ts, most notably adding a notFound parameter to the results as well as parsing everything in to a UserRecord.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LG, thanks!
There's an instance of "deleteUsers()" in line is 680 of auth.d.ts that should be backticked, but we can get that later if it's a PITA.
Not a problem; fixed. |
@@ -1727,6 +1920,35 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |||
}); | |||
}); | |||
|
|||
describe('deleteAccounts', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add tests for rpc logic for this API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also list new types in docgen/content-sources/node/toc.yaml
.
Sorry for not catching this earlier.
@@ -1947,6 +1950,20 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |||
.to.throw(FirebaseAuthError) | |||
.with.property('code', 'auth/invalid-uid'); | |||
}); | |||
|
|||
it('should be fulfilled given a valids uids', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
given valid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
This PR allows callers to retrieve a list of users by unique identifier (uid, email, phone, federated provider uid) as well as to delete a list of users.
RELEASE NOTE: Added
getUsers()
anddeleteUsers()
APIs for retrieving and deleting user accounts in bulk.