-
Couldn't load subscription status.
- Fork 408
feat(auth): Adds support for importUsers API. #220
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
This API can be used for bulk account import to Firebase Auth user database from external Auth systems using different hashing algorithms as well as other Firebase projects. fix(auth): Fixes quota exceeded errors thrown in auth integration tests due to backend throttling.
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.
Code looks correct. Most of my comments are about style and readability.
src/auth/auth.ts
Outdated
| } | ||
|
|
||
| /** | ||
| * Imports the list of users provided to Firebase Auth users database. This is useful when |
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.
to Firebase Auth. sounds good enough I think. The word database in there might confuse somebody.
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
| * When importing a list of password users, UserImportOptions are required to be specified. | ||
| * | ||
| * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth | ||
| * database. |
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.
Remove database from here too?
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/user-import-builder.ts
Outdated
| this.indexMap = {}; | ||
|
|
||
| this.validateAndPopulateUsers(); | ||
| this.validateAndPopulateOptions(); |
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 we refactor the code so the above two lines end up looking like:
this.validatedUsers = this.validateUsers(users);
this.validatedOptions = this.validateOptions(options);
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.
This is not as simple as you think, there are other private variables computed in the process of these. I prefer not to refactor this.
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 made that change but it contains private variables within.
src/auth/user-import-builder.ts
Outdated
| passwordHash: undefined, | ||
| salt: undefined, | ||
| lastLoginAt: undefined, | ||
| createdAt: 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.
Can we not list these undefined fields here? Then you don't have to remove them later in the code.
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 add them so i don't have to define this interface. anyway, i can remove them but will need to define this interface. In addition, other fields could be undefined as passed from developer and still need to be removed. Anyway, i will remove and define the interface even though there is little gained from this.
src/auth/user-import-builder.ts
Outdated
| } | ||
|
|
||
|
|
||
| /** Callback function to validate an object. */ |
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.
Document the contract of this function in more details. What should it do when an invalid user is encountered?
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
| } | ||
|
|
||
| /** | ||
| * Imports the list of users provided to Firebase Auth users database. This is useful when |
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.
Lets drop database from the text. Here and elsewhere.
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/user-import-builder.ts
Outdated
| * Returns the corresponding constructed uploadAccount request. | ||
| * @return {UploadAccountRequest} The constructed uploadAccount request. | ||
| */ | ||
| public generateRequest(): UploadAccountRequest { |
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 buildRequest is a better name for this.
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
test/integration/auth.spec.ts
Outdated
| return safeDelete(randomUid); | ||
| }); | ||
|
|
||
| it('successfully imports users with HMAC_SHA256 hashed passwords', () => { |
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.
Is it possible to organize these tests by creating an array of test fixtures? For example:
interface UserImportTest {
name string
importOptions UserImportOptions
passwordHash string
}
const fixtures: Array<UserImportTest> = [
{
name: 'hmac',
importOptions: {...},
passwordHash: crypto.createHmac(...),
},
...
];
fixtures.forEach((fixture) => {
it(...)
});
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, changing it as described. The passwordHash will be a function so i don't need to hardcode the parameters.
| photoURL: 'http://www.example.com/' + newUserUid + '/photo.png', | ||
| disabled: false, | ||
| }; | ||
| let deleteQueue = Promise.resolve(); |
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.
const?
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 overwrite this at the bottom every time I queue a single delete:
// Suppress errors in delete queue to not spill over to next item in queue.
deleteQueue = deletePromise.catch((error) => {
// Do nothing.
});
return deletePromise;
src/auth/user-import-builder.ts
Outdated
| * @return {UserImportResult} The user import result based on the returned failed | ||
| * uploadAccount response. | ||
| */ | ||
| public generateResponse( |
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.
buildResponse
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
|
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
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 few nits to address, and then I can LGTM.
src/auth/auth.ts
Outdated
| /** | ||
| * Imports the list of users provided to Firebase Auth. This is useful when | ||
| * migrating from an external authentication system without having to use the Firebase CLI SDK. | ||
| * A maximum list of 1000 users are allowed to be imported one at a 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.
Nit: At most 1000 users are....
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/utils/error.ts
Outdated
| }; | ||
| public static INVALID_HASH_ALGORITHM = { | ||
| code: 'invalid-hash-algorithm', | ||
| message: 'The hash algorithm must be a string matching the list of supported algorithms.', |
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: The hash algorithm must match one of the strings in the list 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
test/integration/auth.spec.ts
Outdated
| computePasswordHash: (userImportTest: UserImportTest): Buffer => { | ||
| return Buffer.from(scryptPasswordHash, 'base64'); | ||
| }, | ||
| rawPassword: scryptRawPassword, |
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 these two also be password and NaCl like the other cases? In that case you can extract these fields out of the fixture and just make them shared constants.
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 modified the tests to all use the same rawPassword and rawSalt constants. However, it is not easy to generate the scrypt (internal version) password hash on the fly. The only way to do it is via: https://github.com/firebase/scrypt
test/integration/auth.spec.ts
Outdated
| } | ||
|
|
||
| /** | ||
| * Safe deletes a specificed user identified by uid. This API chains all delete |
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: Safely...
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
| cpuMemCost?: number; | ||
| parallelization?: number; | ||
| blockSize?: number; | ||
| dkLen?: number; |
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 missed this in the previous review. Shall we spell this out like all the others?
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.
This is the backend name. We call this derivedKeyLength (the developer facing one). Check UserImportOptions inteface.
src/auth/user-import-builder.ts
Outdated
| return; | ||
| private populateOptions( | ||
| options: UserImportOptions, requiresHashOptions: boolean): UploadAccountOptions { | ||
| let populatedOptions: UploadAccountOptions = {}; |
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 this be just let populatedOptions: UploadAccountOptions;?
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 actually return this when hash options are required but that is fine, i will return {} directly.
| } | ||
| index++; | ||
| }); | ||
| return populatedUsers; |
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.
If you wanted, you can just return a single array from this method containing both users and errors:
const populatedUsers = [];
users.forEach((user, index) => {
try {
const result = populateUploadAccountUser(user, userValidator);
populatedUsers.push(result);
} catch (error) {
populatedUsers.push({index, error});
}
})
return populatedUsers;
Then in the caller (constructor) you can make all the state changes:
const results = populateUsers(...);
this.validatedUsers = // filter out the users from results
this.indexMap = // construct the index map
this.requireHashOptions = // check if at least one user requires hash
I don't feel strongly about this, so I'll leave it to you.
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.
It is not easy constructing the indexMap after this completes. I have to remember the map original locations of the users to their new locations in the request sent to backend.
src/auth/user-import-builder.ts
Outdated
| let index = 0; | ||
| this.users.forEach((user) => { | ||
| const populatedUsers: UploadAccountUser[] = []; | ||
| users.forEach((user) => { |
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.
You could do users.forEach((user, index) => { here and avoid having to maintain you own index variable.
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
|
If you wish to amend the author of old commits, here's a useful guide: https://www.git-tower.com/learn/git/faq/change-author-name-email You basically has to perform an interactive rebase, and amend each commit with the incorrect email. |
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
OAuth providers. This helps test provider emails are correctly set.
|
Related to #158 |
This API can be used for bulk account import to Firebase Auth user database from external Auth systems using different hashing algorithms as well as other Firebase projects.
Unit tests and integration tests also added for this new API.
fix(auth): Fixes quota exceeded errors thrown in auth integration tests due to backend throttling.