-
Notifications
You must be signed in to change notification settings - Fork 389
feat(auth): Support generate oob code request type VERIFY_AND_CHANGE_EMAIL #1633
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
a350024
Support generate OOB code from VERIFY_AND_CHANGE_EMAIL request type.
Xiaoshouzi-gh dd1a48c
minor lint change
Xiaoshouzi-gh 9206d2e
Added integration test for VERIFY_AND_CHANGE_EMAIL email action link …
Xiaoshouzi-gh 16a8715
fixed lint
Xiaoshouzi-gh 0f4b0fb
address PR feedback
Xiaoshouzi-gh 062b790
Merge branch 'master' into verify-change-email
Xiaoshouzi-gh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 |
---|---|---|
|
@@ -1073,6 +1073,7 @@ describe('admin.auth', () => { | |
describe('Link operations', () => { | ||
const uid = generateRandomString(20).toLowerCase(); | ||
const email = uid + '@example.com'; | ||
const newEmail = uid + '[email protected]'; | ||
const newPassword = 'newPassword'; | ||
const userData = { | ||
uid, | ||
|
@@ -1152,6 +1153,31 @@ describe('admin.auth', () => { | |
expect(result.user!.emailVerified).to.be.true; | ||
}); | ||
}); | ||
|
||
it('generateVerifyAndChangeEmailLink() should return a verification link', function() { | ||
if (authEmulatorHost) { | ||
return this.skip(); // Not yet supported in Auth Emulator. | ||
} | ||
// Ensure the user's email is verified. | ||
return getAuth().updateUser(uid, { password: 'password', emailVerified: true }) | ||
.then((userRecord) => { | ||
expect(userRecord.emailVerified).to.be.true; | ||
return getAuth().generateVerifyAndChangeEmailLink(email, newEmail, actionCodeSettings); | ||
}) | ||
.then((link) => { | ||
const code = getActionCode(link); | ||
expect(getContinueUrl(link)).equal(actionCodeSettings.url); | ||
return clientAuth().applyActionCode(code); | ||
}) | ||
.then(() => { | ||
return clientAuth().signInWithEmailAndPassword(newEmail, 'password'); | ||
}) | ||
.then((result) => { | ||
expect(result.user).to.exist; | ||
expect(result.user!.email).to.equal(newEmail); | ||
expect(result.user!.emailVerified).to.be.true; | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Tenant management operations', () => { | ||
|
This file contains hidden or 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 |
---|---|---|
|
@@ -3065,6 +3065,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
const path = handler.path('v1', '/accounts:sendOobCode', 'project_id'); | ||
const method = 'POST'; | ||
const email = '[email protected]'; | ||
const newEmail = '[email protected]'; | ||
const actionCodeSettings = { | ||
url: 'https://www.example.com/path/file?a=1&b=2', | ||
handleCodeInApp: true, | ||
|
@@ -3110,12 +3111,14 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
requestType, | ||
email, | ||
returnOobLink: true, | ||
...(requestType === 'VERIFY_AND_CHANGE_EMAIL') && { newEmail }, | ||
}, expectedActionCodeSettingsRequest); | ||
const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); | ||
stubs.push(stub); | ||
|
||
const requestHandler = handler.init(mockApp); | ||
return requestHandler.getEmailActionLink(requestType, email, actionCodeSettings) | ||
return requestHandler.getEmailActionLink(requestType, email, actionCodeSettings, | ||
(requestType === 'VERIFY_AND_CHANGE_EMAIL') ? newEmail: undefined) | ||
.then((oobLink: string) => { | ||
expect(oobLink).to.be.equal(expectedLink); | ||
expect(stub).to.have.been.calledOnce.and.calledWith(callParams(path, method, requestData)); | ||
|
@@ -3124,7 +3127,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
}); | ||
|
||
EMAIL_ACTION_REQUEST_TYPES.forEach((requestType) => { | ||
if (requestType === 'EMAIL_SIGNIN') { | ||
if (requestType === 'EMAIL_SIGNIN' || requestType === 'VERIFY_AND_CHANGE_EMAIL') { | ||
return; | ||
} | ||
it('should be fulfilled given requestType:' + requestType + ' and no ActionCodeSettings', () => { | ||
|
@@ -3145,6 +3148,25 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
}); | ||
}); | ||
|
||
it('should be fulfilled given a valid requestType: VERIFY_AND_CHANGE_EMAIL and no ActionCodeSettings', () => { | ||
const VERIFY_AND_CHANGE_EMAIL = 'VERIFY_AND_CHANGE_EMAIL'; | ||
const requestData = { | ||
requestType: VERIFY_AND_CHANGE_EMAIL, | ||
email, | ||
returnOobLink: true, | ||
newEmail, | ||
}; | ||
const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); | ||
stubs.push(stub); | ||
|
||
const requestHandler = handler.init(mockApp); | ||
return requestHandler.getEmailActionLink(VERIFY_AND_CHANGE_EMAIL, email, undefined, newEmail) | ||
.then((oobLink: string) => { | ||
expect(oobLink).to.be.equal(expectedLink); | ||
expect(stub).to.have.been.calledOnce.and.calledWith(callParams(path, method, requestData)); | ||
}); | ||
}); | ||
|
||
it('should be rejected given requestType:EMAIL_SIGNIN and no ActionCodeSettings', () => { | ||
const invalidRequestType = 'EMAIL_SIGNIN'; | ||
const requestHandler = handler.init(mockApp); | ||
|
@@ -3153,6 +3175,22 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
.should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); | ||
}); | ||
|
||
it('should be rejected given requestType: VERIFY_AND_CHANGE and no new Email address', () => { | ||
const requestHandler = handler.init(mockApp); | ||
const expectedError = new FirebaseAuthError( | ||
AuthClientErrorCode.INVALID_ARGUMENT, | ||
'`newEmail` is required when `requestType` === \'VERIFY_AND_CHANGE_EMAIL\'', | ||
) | ||
|
||
return requestHandler.getEmailActionLink('VERIFY_AND_CHANGE_EMAIL', email) | ||
.then(() => { | ||
throw new Error('Unexpected success'); | ||
}, (error) => { | ||
// Invalid argument error should be thrown. | ||
expect(error).to.deep.include(expectedError); | ||
}); | ||
}); | ||
|
||
it('should be rejected given an invalid email', () => { | ||
const invalidEmail = 'invalid'; | ||
const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); | ||
|
@@ -3167,6 +3205,20 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
}); | ||
}); | ||
|
||
it('should be rejected given an invalid new email', () => { | ||
const invalidNewEmail = 'invalid'; | ||
const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_NEW_EMAIL); | ||
|
||
const requestHandler = handler.init(mockApp); | ||
return requestHandler.getEmailActionLink('VERIFY_AND_CHANGE_EMAIL', email, actionCodeSettings, invalidNewEmail) | ||
.then(() => { | ||
throw new Error('Unexpected success'); | ||
}, (error) => { | ||
// Invalid new email error should be thrown. | ||
expect(error).to.deep.include(expectedError); | ||
}); | ||
}); | ||
|
||
it('should be rejected given an invalid request type', () => { | ||
const invalidRequestType = 'invalid'; | ||
const expectedError = new FirebaseAuthError( | ||
|
This file contains hidden or 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 |
---|---|---|
|
@@ -2869,10 +2869,12 @@ AUTH_CONFIGS.forEach((testConfig) => { | |
{ api: 'generatePasswordResetLink', requestType: 'PASSWORD_RESET', requiresSettings: false }, | ||
{ api: 'generateEmailVerificationLink', requestType: 'VERIFY_EMAIL', requiresSettings: false }, | ||
{ api: 'generateSignInWithEmailLink', requestType: 'EMAIL_SIGNIN', requiresSettings: true }, | ||
{ api: 'generateVerifyAndChangeEmailLink', requestType: 'VERIFY_AND_CHANGE_EMAIL', requiresSettings: false }, | ||
]; | ||
emailActionFlows.forEach((emailActionFlow) => { | ||
describe(`${emailActionFlow.api}()`, () => { | ||
const email = '[email protected]'; | ||
const newEmail = '[email protected]'; | ||
const actionCodeSettings = { | ||
url: 'https://www.example.com/path/file?a=1&b=2', | ||
handleCodeInApp: true, | ||
|
@@ -2898,32 +2900,71 @@ AUTH_CONFIGS.forEach((testConfig) => { | |
}); | ||
|
||
it('should be rejected given no email', () => { | ||
return (auth as any)[emailActionFlow.api](undefined, actionCodeSettings) | ||
let args: any = [ undefined, actionCodeSettings ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ undefined, newEmail, actionCodeSettings ]; | ||
} | ||
return (auth as any)[emailActionFlow.api](...args) | ||
.should.eventually.be.rejected.and.have.property('code', 'auth/invalid-email'); | ||
}); | ||
|
||
it('should be rejected given an invalid email', () => { | ||
return (auth as any)[emailActionFlow.api]('invalid', actionCodeSettings) | ||
let args: any = [ 'invalid', actionCodeSettings ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ 'invalid', newEmail, actionCodeSettings ]; | ||
} | ||
return (auth as any)[emailActionFlow.api](...args) | ||
.should.eventually.be.rejected.and.have.property('code', 'auth/invalid-email'); | ||
}); | ||
|
||
it('should be rejected given no new email when request type is `generateVerifyAndChangeEmailLink`', () => { | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
return (auth as any)[emailActionFlow.api](email) | ||
.should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); | ||
} | ||
}); | ||
|
||
it('should be rejected given an invalid new email when request type is `generateVerifyAndChangeEmailLink`', | ||
() => { | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
return (auth as any)[emailActionFlow.api](email, 'invalid') | ||
.should.eventually.be.rejected.and.have.property('code', 'auth/invalid-new-email'); | ||
} | ||
}); | ||
|
||
it('should be rejected given an invalid ActionCodeSettings object', () => { | ||
return (auth as any)[emailActionFlow.api](email, 'invalid') | ||
let args: any = [ email, 'invalid' ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ email, newEmail, 'invalid' ]; | ||
} | ||
return (auth as any)[emailActionFlow.api](...args) | ||
.should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); | ||
}); | ||
|
||
it('should be rejected given an app which returns null access tokens', () => { | ||
return (nullAccessTokenAuth as any)[emailActionFlow.api](email, actionCodeSettings) | ||
let args: any = [ email, actionCodeSettings ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ email, newEmail, actionCodeSettings ]; | ||
} | ||
return (nullAccessTokenAuth as any)[emailActionFlow.api](...args) | ||
.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 as any)[emailActionFlow.api](email, actionCodeSettings) | ||
let args: any = [ email, actionCodeSettings ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ email, newEmail, actionCodeSettings ]; | ||
} | ||
return (malformedAccessTokenAuth as any)[emailActionFlow.api](...args) | ||
.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 as any)[emailActionFlow.api](email, actionCodeSettings) | ||
let args: any = [ email, actionCodeSettings ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ email, newEmail, actionCodeSettings ]; | ||
} | ||
return (rejectedPromiseAccessTokenAuth as any)[emailActionFlow.api](...args) | ||
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); | ||
}); | ||
|
||
|
@@ -2932,7 +2973,11 @@ AUTH_CONFIGS.forEach((testConfig) => { | |
const getEmailActionLinkStub = sinon.stub(testConfig.RequestHandler.prototype, 'getEmailActionLink') | ||
.resolves(expectedLink); | ||
stubs.push(getEmailActionLinkStub); | ||
return (auth as any)[emailActionFlow.api](email, actionCodeSettings) | ||
let args: any = [ email, actionCodeSettings ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ email, newEmail, actionCodeSettings ]; | ||
} | ||
return (auth as any)[emailActionFlow.api](...args) | ||
.then((actualLink: string) => { | ||
// Confirm underlying API called with expected parameters. | ||
expect(getEmailActionLinkStub).to.have.been.calledOnce.and.calledWith( | ||
|
@@ -2953,7 +2998,11 @@ AUTH_CONFIGS.forEach((testConfig) => { | |
const getEmailActionLinkStub = sinon.stub(testConfig.RequestHandler.prototype, 'getEmailActionLink') | ||
.resolves(expectedLink); | ||
stubs.push(getEmailActionLinkStub); | ||
return (auth as any)[emailActionFlow.api](email) | ||
let args: any = [ email ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ email, newEmail ]; | ||
} | ||
return (auth as any)[emailActionFlow.api](...args) | ||
.then((actualLink: string) => { | ||
// Confirm underlying API called with expected parameters. | ||
expect(getEmailActionLinkStub).to.have.been.calledOnce.and.calledWith( | ||
|
@@ -2969,7 +3018,11 @@ AUTH_CONFIGS.forEach((testConfig) => { | |
const getEmailActionLinkStub = sinon.stub(testConfig.RequestHandler.prototype, 'getEmailActionLink') | ||
.rejects(expectedError); | ||
stubs.push(getEmailActionLinkStub); | ||
return (auth as any)[emailActionFlow.api](email, actionCodeSettings) | ||
let args: any = [ email, actionCodeSettings ]; | ||
if (emailActionFlow.api === 'generateVerifyAndChangeEmailLink') { | ||
args = [ email, newEmail, actionCodeSettings ]; | ||
} | ||
return (auth as any)[emailActionFlow.api](...args) | ||
.then(() => { | ||
throw new Error('Unexpected success'); | ||
}, (error: any) => { | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
"email address"
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.
Kept it as email account as the previous public method (verify_email, reset_password etc) uses the same documentation on email paramater. Let me know if you feel strong about
email address
.