-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): Implement getUserByProviderId (#769)
RELEASE NOTE: Added a new getUserByProviderId() to lookup user accounts by their providers.
- Loading branch information
Showing
7 changed files
with
236 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -221,6 +221,56 @@ describe('admin.auth', () => { | |
}); | ||
}); | ||
|
||
it('getUserByProviderUid() returns a user record with the matching provider id', async () => { | ||
// TODO(rsgowman): Once we can link a provider id with a user, just do that | ||
// here instead of creating a new user. | ||
const randomUid = 'import_' + generateRandomString(20).toLowerCase(); | ||
const importUser: admin.auth.UserImportRecord = { | ||
uid: randomUid, | ||
email: '[email protected]', | ||
phoneNumber: '+15555550000', | ||
emailVerified: true, | ||
disabled: false, | ||
metadata: { | ||
lastSignInTime: 'Thu, 01 Jan 1970 00:00:00 UTC', | ||
creationTime: 'Thu, 01 Jan 1970 00:00:00 UTC', | ||
}, | ||
providerData: [{ | ||
displayName: 'User Name', | ||
email: '[email protected]', | ||
phoneNumber: '+15555550000', | ||
photoURL: 'http://example.com/user', | ||
providerId: 'google.com', | ||
uid: 'google_uid', | ||
}], | ||
}; | ||
|
||
await admin.auth().importUsers([importUser]); | ||
|
||
try { | ||
await admin.auth().getUserByProviderUid('google.com', 'google_uid') | ||
.then((userRecord) => { | ||
expect(userRecord.uid).to.equal(importUser.uid); | ||
}); | ||
} finally { | ||
await safeDelete(importUser.uid); | ||
} | ||
}); | ||
|
||
it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { | ||
return admin.auth().getUserByProviderUid('email', mockUserData.email) | ||
.then((userRecord) => { | ||
expect(userRecord.uid).to.equal(newUserUid); | ||
}); | ||
}); | ||
|
||
it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { | ||
return admin.auth().getUserByProviderUid('phone', mockUserData.phoneNumber) | ||
.then((userRecord) => { | ||
expect(userRecord.uid).to.equal(newUserUid); | ||
}); | ||
}); | ||
|
||
describe('getUsers()', () => { | ||
/** | ||
* Filters a list of object to another list of objects that only contains | ||
|
@@ -623,6 +673,11 @@ describe('admin.auth', () => { | |
.should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); | ||
}); | ||
|
||
it('getUserByProviderUid() fails when called with a non-existing provider id', () => { | ||
return admin.auth().getUserByProviderUid('google.com', nonexistentUid) | ||
.should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); | ||
}); | ||
|
||
it('updateUser() fails when called with a non-existing UID', () => { | ||
return admin.auth().updateUser(nonexistentUid, { | ||
emailVerified: true, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1151,6 +1151,120 @@ AUTH_CONFIGS.forEach((testConfig) => { | |
}); | ||
}); | ||
|
||
describe('getUserByProviderUid()', () => { | ||
const providerId = 'google.com'; | ||
const providerUid = 'google_uid'; | ||
const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; | ||
const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); | ||
const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); | ||
const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); | ||
|
||
// Stubs used to simulate underlying api calls. | ||
let stubs: sinon.SinonStub[] = []; | ||
beforeEach(() => sinon.spy(validator, 'isEmail')); | ||
afterEach(() => { | ||
(validator.isEmail as any).restore(); | ||
_.forEach(stubs, (stub) => stub.restore()); | ||
stubs = []; | ||
}); | ||
|
||
it('should be rejected given no provider id', () => { | ||
expect(() => (auth as any).getUserByProviderUid()) | ||
.to.throw(FirebaseAuthError) | ||
.with.property('code', 'auth/invalid-provider-id'); | ||
}); | ||
|
||
it('should be rejected given an invalid provider id', () => { | ||
expect(() => auth.getUserByProviderUid('', 'uid')) | ||
.to.throw(FirebaseAuthError) | ||
.with.property('code', 'auth/invalid-provider-id'); | ||
}); | ||
|
||
it('should be rejected given an invalid provider uid', () => { | ||
expect(() => auth.getUserByProviderUid('id', '')) | ||
.to.throw(FirebaseAuthError) | ||
.with.property('code', 'auth/invalid-provider-id'); | ||
}); | ||
|
||
it('should be rejected given an app which returns null access tokens', () => { | ||
return nullAccessTokenAuth.getUserByProviderUid(providerId, providerUid) | ||
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); | ||
}); | ||
|
||
it('should be rejected given an app which returns invalid access tokens', () => { | ||
return malformedAccessTokenAuth.getUserByProviderUid(providerId, providerUid) | ||
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); | ||
}); | ||
|
||
it('should be rejected given an app which fails to generate access tokens', () => { | ||
return rejectedPromiseAccessTokenAuth.getUserByProviderUid(providerId, providerUid) | ||
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); | ||
}); | ||
|
||
it('should resolve with a UserRecord on success', () => { | ||
// Stub getAccountInfoByEmail to return expected result. | ||
const stub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByFederatedUid') | ||
.resolves(expectedGetAccountInfoResult); | ||
stubs.push(stub); | ||
return auth.getUserByProviderUid(providerId, providerUid) | ||
.then((userRecord) => { | ||
// Confirm underlying API called with expected parameters. | ||
expect(stub).to.have.been.calledOnce.and.calledWith(providerId, providerUid); | ||
// Confirm expected user record response returned. | ||
expect(userRecord).to.deep.equal(expectedUserRecord); | ||
}); | ||
}); | ||
|
||
describe('non-federated providers', () => { | ||
let invokeRequestHandlerStub: sinon.SinonStub; | ||
beforeEach(() => { | ||
invokeRequestHandlerStub = sinon.stub(testConfig.RequestHandler.prototype, 'invokeRequestHandler') | ||
.resolves({ | ||
// nothing here is checked; we just need enough to not crash. | ||
users: [{ | ||
localId: 1, | ||
}], | ||
}); | ||
|
||
}); | ||
afterEach(() => { | ||
invokeRequestHandlerStub.restore(); | ||
}); | ||
|
||
it('phone lookups should use phoneNumber field', async () => { | ||
await auth.getUserByProviderUid('phone', '+15555550001'); | ||
expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( | ||
sinon.match.any, sinon.match.any, { | ||
phoneNumber: ['+15555550001'], | ||
}); | ||
}); | ||
|
||
it('email lookups should use email field', async () => { | ||
await auth.getUserByProviderUid('email', '[email protected]'); | ||
expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( | ||
sinon.match.any, sinon.match.any, { | ||
email: ['[email protected]'], | ||
}); | ||
}); | ||
}); | ||
|
||
it('should throw an error when the backend returns an error', () => { | ||
// Stub getAccountInfoByFederatedUid to throw a backend error. | ||
const stub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByFederatedUid') | ||
.rejects(expectedError); | ||
stubs.push(stub); | ||
return auth.getUserByProviderUid(providerId, providerUid) | ||
.then(() => { | ||
throw new Error('Unexpected success'); | ||
}, (error) => { | ||
// Confirm underlying API called with expected parameters. | ||
expect(stub).to.have.been.calledOnce.and.calledWith(providerId, providerUid); | ||
// Confirm expected error returned. | ||
expect(error).to.equal(expectedError); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('getUsers()', () => { | ||
let stubs: sinon.SinonStub[] = []; | ||
|
||
|