-
Notifications
You must be signed in to change notification settings - Fork 391
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
Changes from 4 commits
a350024
dd1a48c
9206d2e
16a8715
0f4b0fb
062b790
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -827,6 +827,35 @@ export abstract class BaseAuth { | |
return this.authRequestHandler.getEmailActionLink('VERIFY_EMAIL', email, actionCodeSettings); | ||
} | ||
|
||
/** | ||
* Generates the out of band email action link to verify the user's ownership | ||
Xiaoshouzi-gh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* of the specified email. The {@link ActionCodeSettings} object provided | ||
* as an argument to this method defines whether the link is to be handled by a | ||
* mobile app or browser along with additional state information to be passed in | ||
* the deep link, etc. | ||
* | ||
* @param email - The current email account. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 |
||
* @param newEmail - The email address the account is being updated to. | ||
* @param actionCodeSettings - The action | ||
* code settings. If specified, the state/continue URL is set as the | ||
* "continueUrl" parameter in the email verification link. The default email | ||
* verification landing page will use this to display a link to go back to | ||
* the app if it is installed. | ||
* If the actionCodeSettings is not specified, no URL is appended to the | ||
* action URL. | ||
* The state URL provided must belong to a domain that is whitelisted by the | ||
* developer in the console. Otherwise an error is thrown. | ||
Xiaoshouzi-gh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Mobile app redirects are only applicable if the developer configures | ||
* and accepts the Firebase Dynamic Links terms of service. | ||
* The Android package name and iOS bundle ID are respected only if they | ||
* are configured in the same Firebase Auth project. | ||
* @returns A promise that resolves with the generated link. | ||
*/ | ||
public generateVerifyAndChangeEmailLink(email: string, newEmail: string, | ||
actionCodeSettings?: ActionCodeSettings): Promise<string> { | ||
return this.authRequestHandler.getEmailActionLink('VERIFY_AND_CHANGE_EMAIL', email, actionCodeSettings, newEmail); | ||
} | ||
|
||
/** | ||
* Generates the out of band email action link to verify the user's ownership | ||
* of the specified email. The {@link ActionCodeSettings} object provided | ||
|
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', () => { | ||
|
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, | ||
|
@@ -3106,6 +3107,9 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
|
||
EMAIL_ACTION_REQUEST_TYPES.forEach((requestType) => { | ||
it('should be fulfilled given a valid requestType:' + requestType + ' and ActionCodeSettings', () => { | ||
if (requestType === 'VERIFY_AND_CHANGE_EMAIL') { | ||
return; | ||
Xiaoshouzi-gh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
const requestData = deepExtend({ | ||
requestType, | ||
email, | ||
|
@@ -3123,8 +3127,27 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { | |
}); | ||
}); | ||
|
||
it('should be fulfilled given a valid requestType: VERIFY_AND_CHANGE_EMAIL and ActionCodeSettings', () => { | ||
const VERIFY_AND_CHANGE_EMAIL = 'VERIFY_AND_CHANGE_EMAIL'; | ||
const requestData = deepExtend({ | ||
requestType: VERIFY_AND_CHANGE_EMAIL, | ||
email, | ||
returnOobLink: true, | ||
newEmail | ||
}, expectedActionCodeSettingsRequest); | ||
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, actionCodeSettings, newEmail) | ||
.then((oobLink: string) => { | ||
expect(oobLink).to.be.equal(expectedLink); | ||
expect(stub).to.have.been.calledOnce.and.calledWith(callParams(path, method, requestData)); | ||
}); | ||
}); | ||
|
||
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 +3168,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 +3195,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 +3225,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( | ||
|
Uh oh!
There was an error while loading. Please reload this page.