Skip to content

Commit 47c5056

Browse files
authored
Remove (base64) 'REDACTED' passwords from user records. (#660)
These values *look* like passwords hashes, but aren't, leading to potential confusion. Additionally, added docs to CONTRIBUTING.md detailing how to add the permission that causes password hashes to be properly returned as well as adjusting the test failure message should the developer not add that permission. b/141189502
1 parent ede5018 commit 47c5056

File tree

5 files changed

+47
-3
lines changed

5 files changed

+47
-3
lines changed

CONTRIBUTING.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,21 @@ Then set up your Firebase/GCP project as follows:
153153
to set up Firestore either in the locked mode or in the test mode.
154154
2. Enable password auth: Select "Authentication" from the "Develop" menu in
155155
Firebase Console. Select the "Sign-in method" tab, and enable the
156-
"Email/Password" sign-in method.
156+
"Email/Password" sign-in method, including the Email link (passwordless
157+
sign-in) option.
157158
3. Enable the IAM API: Go to the
158159
[Google Cloud Platform Console](https://console.cloud.google.com) and make
159160
sure your Firebase/GCP project is selected. Select "APIs & Services >
160161
Dashboard" from the main menu, and click the "ENABLE APIS AND SERVICES"
161162
button. Search for and enable the "Identity and Access Management (IAM)
162163
API".
164+
4. Grant your service account the 'Firebase Authentication Admin' role. This is
165+
required to ensure that exported user records contain the password hashes of
166+
the user accounts:
167+
1. Go to [Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin).
168+
2. Find your service account in the list, and click the 'pencil' icon to edit it's permissions.
169+
3. Click 'ADD ANOTHER ROLE' and choose 'Firebase Authentication Admin'.
170+
4. Click 'SAVE'.
163171

164172
Finally, to run the integration test suite:
165173

src/auth/user-record.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import {deepCopy} from '../utils/deep-copy';
1818
import * as utils from '../utils';
1919
import {AuthClientErrorCode, FirebaseAuthError} from '../utils/error';
2020

21+
/**
22+
* 'REDACTED', encoded as a base64 string.
23+
*/
24+
const B64_REDACTED = Buffer.from('REDACTED').toString('base64');
25+
2126
/**
2227
* Parses a time stamp string or number and returns the corresponding date if valid.
2328
*
@@ -173,7 +178,16 @@ export class UserRecord {
173178
providerData.push(new UserInfo(entry));
174179
}
175180
utils.addReadonlyGetter(this, 'providerData', providerData);
176-
utils.addReadonlyGetter(this, 'passwordHash', response.passwordHash);
181+
182+
// If the password hash is redacted (probably due to missing permissions)
183+
// then clear it out, similar to how the salt is returned. (Otherwise, it
184+
// *looks* like a b64-encoded hash is present, which is confusing.)
185+
if (response.passwordHash === B64_REDACTED) {
186+
utils.addReadonlyGetter(this, 'passwordHash', undefined);
187+
} else {
188+
utils.addReadonlyGetter(this, 'passwordHash', response.passwordHash);
189+
}
190+
177191
utils.addReadonlyGetter(this, 'passwordSalt', response.salt);
178192
try {
179193
utils.addReadonlyGetter(

test/integration/auth.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,21 @@ describe('admin.auth', () => {
185185
expect(typeof listUsersResult.pageToken).to.equal('string');
186186
// Confirm each user's uid and the hashed passwords.
187187
expect(listUsersResult.users[0].uid).to.equal(uids[1]);
188+
189+
expect(
190+
listUsersResult.users[0].passwordHash,
191+
'Missing passwordHash field. A common cause would be forgetting to '
192+
+ 'add the "Firebase Authentication Admin" permission. See '
193+
+ 'instructions in CONTRIBUTING.md',
194+
).to.be.ok;
188195
expect(listUsersResult.users[0].passwordHash.length).greaterThan(0);
196+
197+
expect(
198+
listUsersResult.users[0].passwordSalt,
199+
'Missing passwordSalt field. A common cause would be forgetting to '
200+
+ 'add the "Firebase Authentication Admin" permission. See '
201+
+ 'instructions in CONTRIBUTING.md',
202+
).to.be.ok;
189203
expect(listUsersResult.users[0].passwordSalt.length).greaterThan(0);
190204

191205
expect(listUsersResult.users[1].uid).to.equal(uids[2]);

test/unit/auth/auth.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1684,7 +1684,6 @@ AUTH_CONFIGS.forEach((testConfig) => {
16841684
});
16851685
});
16861686

1687-
16881687
it('should resolve on downloadAccount request success with no users in response', () => {
16891688
// Stub downloadAccount to return expected response.
16901689
const downloadAccountStub = sinon

test/unit/auth/user-record.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,15 @@ describe('UserRecord', () => {
486486
expect((new UserRecord(resp)).passwordHash).to.be.undefined;
487487
});
488488

489+
it('should clear REDACTED passwordHash', () => {
490+
const user = new UserRecord({
491+
localId: 'uid1',
492+
passwordHash: Buffer.from('REDACTED').toString('base64'),
493+
});
494+
495+
expect(user.passwordHash).to.be.undefined;
496+
});
497+
489498
it('should return expected empty string passwordHash', () => {
490499
// This happens for users that were migrated from other Auth systems
491500
// using different hashing algorithms.

0 commit comments

Comments
 (0)