mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #17572 from overleaf/tm-email-confirmation-require-login
Prevent email confirmation unless logged in to the requesting account GitOrigin-RevId: 28af875b2887b8bbef8327097635aa01345c682c
This commit is contained in:
parent
811173d32d
commit
8a04ec9b75
7 changed files with 188 additions and 131 deletions
|
@ -7,6 +7,7 @@ const UserUpdater = require('./UserUpdater')
|
||||||
const UserGetter = require('./UserGetter')
|
const UserGetter = require('./UserGetter')
|
||||||
const { callbackify, promisify } = require('util')
|
const { callbackify, promisify } = require('util')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
const SessionManager = require('../Authentication/SessionManager')
|
||||||
|
|
||||||
// Reject email confirmation tokens after 90 days
|
// Reject email confirmation tokens after 90 days
|
||||||
const TOKEN_EXPIRY_IN_S = 90 * 24 * 60 * 60
|
const TOKEN_EXPIRY_IN_S = 90 * 24 * 60 * 60
|
||||||
|
@ -85,58 +86,51 @@ async function sendReconfirmationEmail(userId, email) {
|
||||||
await EmailHandler.promises.sendEmail('reconfirmEmail', emailOptions)
|
await EmailHandler.promises.sendEmail('reconfirmEmail', emailOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function confirmEmailFromToken(req, token) {
|
||||||
|
const { data } = await OneTimeTokenHandler.promises.peekValueFromToken(
|
||||||
|
TOKEN_USE,
|
||||||
|
token
|
||||||
|
)
|
||||||
|
if (!data) {
|
||||||
|
throw new Errors.NotFoundError('no token found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const loggedInUserId = SessionManager.getLoggedInUserId(req.session)
|
||||||
|
// user_id may be stored as an ObjectId or string
|
||||||
|
const userId = data.user_id?.toString()
|
||||||
|
const email = data.email
|
||||||
|
if (!userId || email !== EmailHelper.parseEmail(email)) {
|
||||||
|
throw new Errors.NotFoundError('invalid data')
|
||||||
|
}
|
||||||
|
if (loggedInUserId !== userId) {
|
||||||
|
throw new Errors.ForbiddenError('logged in user does not match token user')
|
||||||
|
}
|
||||||
|
const user = await UserGetter.promises.getUser(userId, { emails: 1 })
|
||||||
|
if (!user) {
|
||||||
|
throw new Errors.NotFoundError('user not found')
|
||||||
|
}
|
||||||
|
const emailExists = user.emails.some(emailData => emailData.email === email)
|
||||||
|
if (!emailExists) {
|
||||||
|
throw new Errors.NotFoundError('email missing for user')
|
||||||
|
}
|
||||||
|
|
||||||
|
await OneTimeTokenHandler.promises.expireToken(TOKEN_USE, token)
|
||||||
|
await UserUpdater.promises.confirmEmail(userId, email)
|
||||||
|
|
||||||
|
return { userId, email }
|
||||||
|
}
|
||||||
|
|
||||||
const UserEmailsConfirmationHandler = {
|
const UserEmailsConfirmationHandler = {
|
||||||
sendConfirmationEmail,
|
sendConfirmationEmail,
|
||||||
|
|
||||||
sendReconfirmationEmail: callbackify(sendReconfirmationEmail),
|
sendReconfirmationEmail: callbackify(sendReconfirmationEmail),
|
||||||
|
|
||||||
confirmEmailFromToken(token, callback) {
|
confirmEmailFromToken: callbackify(confirmEmailFromToken),
|
||||||
OneTimeTokenHandler.getValueFromTokenAndExpire(
|
|
||||||
TOKEN_USE,
|
|
||||||
token,
|
|
||||||
function (error, data) {
|
|
||||||
if (error) {
|
|
||||||
return callback(error)
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
return callback(new Errors.NotFoundError('no token found'))
|
|
||||||
}
|
|
||||||
const userId = data.user_id
|
|
||||||
const email = data.email
|
|
||||||
|
|
||||||
if (!userId || email !== EmailHelper.parseEmail(email)) {
|
|
||||||
return callback(new Errors.NotFoundError('invalid data'))
|
|
||||||
}
|
|
||||||
UserGetter.getUser(userId, { emails: 1 }, function (error, user) {
|
|
||||||
if (error) {
|
|
||||||
return callback(error)
|
|
||||||
}
|
|
||||||
if (!user) {
|
|
||||||
return callback(new Errors.NotFoundError('user not found'))
|
|
||||||
}
|
|
||||||
const emailExists = user.emails.some(
|
|
||||||
emailData => emailData.email === email
|
|
||||||
)
|
|
||||||
if (!emailExists) {
|
|
||||||
return callback(new Errors.NotFoundError('email missing for user'))
|
|
||||||
}
|
|
||||||
UserUpdater.confirmEmail(userId, email, function (error) {
|
|
||||||
if (error) {
|
|
||||||
return callback(error)
|
|
||||||
}
|
|
||||||
callback(null, { userId, email })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UserEmailsConfirmationHandler.promises = {
|
UserEmailsConfirmationHandler.promises = {
|
||||||
sendConfirmationEmail: promisify(sendConfirmationEmail),
|
sendConfirmationEmail: promisify(sendConfirmationEmail),
|
||||||
confirmEmailFromToken: promisify(
|
confirmEmailFromToken,
|
||||||
UserEmailsConfirmationHandler.confirmEmailFromToken
|
|
||||||
),
|
|
||||||
sendConfirmationCode,
|
sendConfirmationCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,13 @@ async function primaryEmailCheck(req, res) {
|
||||||
AsyncFormHelper.redirect(req, res, '/project')
|
AsyncFormHelper.redirect(req, res, '/project')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function showConfirm(req, res, next) {
|
||||||
|
res.render('user/confirm_email', {
|
||||||
|
token: req.query.token,
|
||||||
|
title: 'confirm_email',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const UserEmailsController = {
|
const UserEmailsController = {
|
||||||
list(req, res, next) {
|
list(req, res, next) {
|
||||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
|
@ -254,12 +261,7 @@ const UserEmailsController = {
|
||||||
|
|
||||||
primaryEmailCheck: expressify(primaryEmailCheck),
|
primaryEmailCheck: expressify(primaryEmailCheck),
|
||||||
|
|
||||||
showConfirm(req, res, next) {
|
showConfirm: expressify(showConfirm),
|
||||||
res.render('user/confirm_email', {
|
|
||||||
token: req.query.token,
|
|
||||||
title: 'confirm_email',
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
confirm(req, res, next) {
|
confirm(req, res, next) {
|
||||||
const { token } = req.body
|
const { token } = req.body
|
||||||
|
@ -269,10 +271,18 @@ const UserEmailsController = {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
UserEmailsConfirmationHandler.confirmEmailFromToken(
|
UserEmailsConfirmationHandler.confirmEmailFromToken(
|
||||||
|
req,
|
||||||
token,
|
token,
|
||||||
function (error, userData) {
|
function (error, userData) {
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error instanceof Errors.NotFoundError) {
|
if (error instanceof Errors.ForbiddenError) {
|
||||||
|
res.status(403).json({
|
||||||
|
message: {
|
||||||
|
key: 'confirm-email-wrong-user',
|
||||||
|
text: `We can’t confirm this email. You must be logged in with the Overleaf account that requested the new secondary email.`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if (error instanceof Errors.NotFoundError) {
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
message: req.i18n.translate('confirmation_token_invalid'),
|
message: req.i18n.translate('confirmation_token_invalid'),
|
||||||
})
|
})
|
||||||
|
|
|
@ -303,9 +303,14 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
UserController.promises.ensureAffiliationMiddleware,
|
UserController.promises.ensureAffiliationMiddleware,
|
||||||
UserEmailsController.list
|
UserEmailsController.list
|
||||||
)
|
)
|
||||||
webRouter.get('/user/emails/confirm', UserEmailsController.showConfirm)
|
webRouter.get(
|
||||||
|
'/user/emails/confirm',
|
||||||
|
AuthenticationController.requireLogin(),
|
||||||
|
UserEmailsController.showConfirm
|
||||||
|
)
|
||||||
webRouter.post(
|
webRouter.post(
|
||||||
'/user/emails/confirm',
|
'/user/emails/confirm',
|
||||||
|
AuthenticationController.requireLogin(),
|
||||||
RateLimiterMiddleware.rateLimit(rateLimiters.confirmEmail),
|
RateLimiterMiddleware.rateLimit(rateLimiters.confirmEmail),
|
||||||
UserEmailsController.confirm
|
UserEmailsController.confirm
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,10 +4,17 @@ block content
|
||||||
main.content.content-alt#main-content
|
main.content.content-alt#main-content
|
||||||
.container
|
.container
|
||||||
.row
|
.row
|
||||||
.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
|
.col-md-8.col-md-offset-2.col-lg-6.col-lg-offset-3
|
||||||
.card
|
.card
|
||||||
.page-header
|
.page-header(data-ol-hide-on-error-message="confirm-email-wrong-user")
|
||||||
h1 #{translate("confirm_email")}
|
h1 #{translate("confirm_email")}
|
||||||
|
form(
|
||||||
|
method="POST"
|
||||||
|
action="/logout"
|
||||||
|
id="logoutForm"
|
||||||
|
)
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
|
input(type="hidden", name="redirect", value=currentUrlWithQueryParams)
|
||||||
form(
|
form(
|
||||||
data-ol-async-form,
|
data-ol-async-form,
|
||||||
data-ol-auto-submit,
|
data-ol-auto-submit,
|
||||||
|
@ -21,11 +28,20 @@ block content
|
||||||
|
|
||||||
div(data-ol-not-sent)
|
div(data-ol-not-sent)
|
||||||
+formMessages()
|
+formMessages()
|
||||||
|
div(data-ol-custom-form-message="confirm-email-wrong-user" hidden)
|
||||||
|
h1.h3 #{translate("we_cant_confirm_this_email")}
|
||||||
|
p !{translate("to_confirm_email_address_you_must_be_logged_in_with_the_requesting_account")}
|
||||||
|
p !{translate("you_are_currently_logged_in_as", {email: getUserEmail()})}
|
||||||
|
.actions
|
||||||
|
button.btn-primary.btn.btn-block(
|
||||||
|
form="logoutForm"
|
||||||
|
) #{translate('log_in_with_a_different_account')}
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
button.btn-primary.btn.btn-block(
|
button.btn-primary.btn.btn-block(
|
||||||
type='submit',
|
type='submit',
|
||||||
data-ol-disabled-inflight
|
data-ol-disabled-inflight
|
||||||
|
data-ol-hide-on-error-message="confirm-email-wrong-user"
|
||||||
)
|
)
|
||||||
span(data-ol-inflight="idle")
|
span(data-ol-inflight="idle")
|
||||||
| #{translate('confirm')}
|
| #{translate('confirm')}
|
||||||
|
|
|
@ -1029,6 +1029,7 @@
|
||||||
"log_in_first_to_proceed": "You will need to <b>log in</b> first to proceed.",
|
"log_in_first_to_proceed": "You will need to <b>log in</b> first to proceed.",
|
||||||
"log_in_now": "Log in now",
|
"log_in_now": "Log in now",
|
||||||
"log_in_with": "Log in with __provider__",
|
"log_in_with": "Log in with __provider__",
|
||||||
|
"log_in_with_a_different_account": "Log in with a different account",
|
||||||
"log_in_with_email": "Log in with __email__",
|
"log_in_with_email": "Log in with __email__",
|
||||||
"log_in_with_existing_institution_email": "Please log in with your existing <b>__appName__</b> account in order to get your <b>__appName__</b> and <b>__institutionName__</b> institutional accounts linked.",
|
"log_in_with_existing_institution_email": "Please log in with your existing <b>__appName__</b> account in order to get your <b>__appName__</b> and <b>__institutionName__</b> institutional accounts linked.",
|
||||||
"log_in_with_primary_email_address": "This will be the email address to use if you log in with an email address and password. Important __appName__ notifications will be sent to this email address.",
|
"log_in_with_primary_email_address": "This will be the email address to use if you log in with an email address and password. Important __appName__ notifications will be sent to this email address.",
|
||||||
|
@ -1913,6 +1914,7 @@
|
||||||
"to_add_email_accounts_need_to_be_linked_2": "To add this email, your <0>__appName__</0> and <0>__institutionName__</0> accounts will need to be linked.",
|
"to_add_email_accounts_need_to_be_linked_2": "To add this email, your <0>__appName__</0> and <0>__institutionName__</0> accounts will need to be linked.",
|
||||||
"to_add_more_collaborators": "To add more collaborators or turn on link sharing, please ask the project owner",
|
"to_add_more_collaborators": "To add more collaborators or turn on link sharing, please ask the project owner",
|
||||||
"to_change_access_permissions": "To change access permissions, please ask the project owner",
|
"to_change_access_permissions": "To change access permissions, please ask the project owner",
|
||||||
|
"to_confirm_email_address_you_must_be_logged_in_with_the_requesting_account": "To confirm an email address, you <b>must be logged in</b> with the <b>Overleaf account that requested the new secondary email</b>.",
|
||||||
"to_confirm_transfer_enter_email_address": "To accept the invitation, enter the email address linked to your account.",
|
"to_confirm_transfer_enter_email_address": "To accept the invitation, enter the email address linked to your account.",
|
||||||
"to_confirm_unlink_all_users_enter_email": "To confirm you want to unlink all users, enter your email address:",
|
"to_confirm_unlink_all_users_enter_email": "To confirm you want to unlink all users, enter your email address:",
|
||||||
"to_fix_this_you_can": "To fix this, you can:",
|
"to_fix_this_you_can": "To fix this, you can:",
|
||||||
|
@ -2121,6 +2123,7 @@
|
||||||
"visual_editor": "Visual Editor",
|
"visual_editor": "Visual Editor",
|
||||||
"visual_editor_is_only_available_for_tex_files": "Visual Editor is only available for TeX files",
|
"visual_editor_is_only_available_for_tex_files": "Visual Editor is only available for TeX files",
|
||||||
"want_change_to_apply_before_plan_end": "If you wish this change to apply before the end of your current billing period, please contact us.",
|
"want_change_to_apply_before_plan_end": "If you wish this change to apply before the end of your current billing period, please contact us.",
|
||||||
|
"we_cant_confirm_this_email": "We can’t confirm this email",
|
||||||
"we_cant_find_any_sections_or_subsections_in_this_file": "We can’t find any sections or subsections in this file",
|
"we_cant_find_any_sections_or_subsections_in_this_file": "We can’t find any sections or subsections in this file",
|
||||||
"we_do_not_share_personal_information": "See our <0>Privacy Notice</0> for details of how we treat your personal data",
|
"we_do_not_share_personal_information": "See our <0>Privacy Notice</0> for details of how we treat your personal data",
|
||||||
"we_logged_you_in": "We have logged you in.",
|
"we_logged_you_in": "We have logged you in.",
|
||||||
|
@ -2179,6 +2182,7 @@
|
||||||
"you_are_a_manager_of_commons_at_institution_x": "You are a <0>manager</0> of the Overleaf Commons subscription at <0>__institutionName__</0>",
|
"you_are_a_manager_of_commons_at_institution_x": "You are a <0>manager</0> of the Overleaf Commons subscription at <0>__institutionName__</0>",
|
||||||
"you_are_a_manager_of_publisher_x": "You are a <0>manager</0> of <0>__publisherName__</0>",
|
"you_are_a_manager_of_publisher_x": "You are a <0>manager</0> of <0>__publisherName__</0>",
|
||||||
"you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager</1> of the <0>__planName__</0> group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
|
"you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager</1> of the <0>__planName__</0> group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
|
||||||
|
"you_are_currently_logged_in_as": "You are currently logged in as <b>__email__</b>.",
|
||||||
"you_are_on_a_paid_plan_contact_support_to_find_out_more": "You’re on an __appName__ Paid plan. <0>Contact support</0> to find out more.",
|
"you_are_on_a_paid_plan_contact_support_to_find_out_more": "You’re on an __appName__ Paid plan. <0>Contact support</0> to find out more.",
|
||||||
"you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "You are on our <0>__planName__</0> plan as a <1>confirmed member</1> of <1>__institutionName__</1>",
|
"you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "You are on our <0>__planName__</0> plan as a <1>confirmed member</1> of <1>__institutionName__</1>",
|
||||||
"you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are on our <0>__planName__</0> plan as a <1>member</1> of the group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
|
"you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are on our <0>__planName__</0> plan as a <1>member</1> of the group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
|
||||||
|
|
|
@ -24,20 +24,6 @@ const EmailHelper = require('../../../../app/src/Features/Helpers/EmailHelper')
|
||||||
|
|
||||||
describe('UserEmailsConfirmationHandler', function () {
|
describe('UserEmailsConfirmationHandler', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.UserEmailsConfirmationHandler = SandboxedModule.require(modulePath, {
|
|
||||||
requires: {
|
|
||||||
'@overleaf/settings': (this.settings = {
|
|
||||||
siteUrl: 'https://emails.example.com',
|
|
||||||
}),
|
|
||||||
'../Security/OneTimeTokenHandler': (this.OneTimeTokenHandler = {}),
|
|
||||||
'./UserUpdater': (this.UserUpdater = {}),
|
|
||||||
'./UserGetter': (this.UserGetter = {
|
|
||||||
getUser: sinon.stub().yields(null, this.mockUser),
|
|
||||||
}),
|
|
||||||
'../Email/EmailHandler': (this.EmailHandler = {}),
|
|
||||||
'../Helpers/EmailHelper': EmailHelper,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
this.mockUser = {
|
this.mockUser = {
|
||||||
_id: 'mock-user-id',
|
_id: 'mock-user-id',
|
||||||
email: 'mock@example.com',
|
email: 'mock@example.com',
|
||||||
|
@ -45,6 +31,31 @@ describe('UserEmailsConfirmationHandler', function () {
|
||||||
}
|
}
|
||||||
this.user_id = this.mockUser._id
|
this.user_id = this.mockUser._id
|
||||||
this.email = this.mockUser.email
|
this.email = this.mockUser.email
|
||||||
|
this.req = {}
|
||||||
|
this.UserEmailsConfirmationHandler = SandboxedModule.require(modulePath, {
|
||||||
|
requires: {
|
||||||
|
'@overleaf/settings': (this.settings = {
|
||||||
|
siteUrl: 'https://emails.example.com',
|
||||||
|
}),
|
||||||
|
'../Security/OneTimeTokenHandler': (this.OneTimeTokenHandler = {
|
||||||
|
promises: {},
|
||||||
|
}),
|
||||||
|
'./UserUpdater': (this.UserUpdater = {
|
||||||
|
promises: {},
|
||||||
|
}),
|
||||||
|
'./UserGetter': (this.UserGetter = {
|
||||||
|
getUser: sinon.stub().yields(null, this.mockUser),
|
||||||
|
promises: {
|
||||||
|
getUser: sinon.stub().resolves(this.mockUser),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'../Email/EmailHandler': (this.EmailHandler = {}),
|
||||||
|
'../Helpers/EmailHelper': EmailHelper,
|
||||||
|
'../Authentication/SessionManager': (this.SessionManager = {
|
||||||
|
getLoggedInUserId: sinon.stub().returns(this.mockUser._id),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
return (this.callback = sinon.stub())
|
return (this.callback = sinon.stub())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -127,122 +138,139 @@ describe('UserEmailsConfirmationHandler', function () {
|
||||||
|
|
||||||
describe('confirmEmailFromToken', function () {
|
describe('confirmEmailFromToken', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.OneTimeTokenHandler.getValueFromTokenAndExpire = sinon
|
this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.yields(null, { user_id: this.user_id, email: this.email })
|
.resolves({ data: { user_id: this.user_id, email: this.email } })
|
||||||
return (this.UserUpdater.confirmEmail = sinon.stub().yields())
|
this.OneTimeTokenHandler.promises.expireToken = sinon.stub().resolves()
|
||||||
|
this.UserUpdater.promises.confirmEmail = sinon.stub().resolves()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('successfully', function () {
|
describe('successfully', function () {
|
||||||
beforeEach(function () {
|
beforeEach(async function () {
|
||||||
return this.UserEmailsConfirmationHandler.confirmEmailFromToken(
|
await this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
|
||||||
(this.token = 'mock-token'),
|
this.req,
|
||||||
this.callback
|
(this.token = 'mock-token')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call getValueFromTokenAndExpire', function () {
|
it('should call peekValueFromToken', function () {
|
||||||
return this.OneTimeTokenHandler.getValueFromTokenAndExpire
|
return this.OneTimeTokenHandler.promises.peekValueFromToken
|
||||||
|
.calledWith('email_confirmation', this.token)
|
||||||
|
.should.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call expireToken', function () {
|
||||||
|
return this.OneTimeTokenHandler.promises.expireToken
|
||||||
.calledWith('email_confirmation', this.token)
|
.calledWith('email_confirmation', this.token)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should confirm the email of the user_id', function () {
|
it('should confirm the email of the user_id', function () {
|
||||||
return this.UserUpdater.confirmEmail
|
return this.UserUpdater.promises.confirmEmail
|
||||||
.calledWith(this.user_id, this.email)
|
.calledWith(this.user_id, this.email)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call the callback', function () {
|
|
||||||
return this.callback.called.should.equal(true)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with an expired token', function () {
|
describe('with an expired token', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.OneTimeTokenHandler.getValueFromTokenAndExpire = sinon
|
this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.yields(null, null)
|
.rejects(new Errors.NotFoundError('no token found'))
|
||||||
return this.UserEmailsConfirmationHandler.confirmEmailFromToken(
|
|
||||||
(this.token = 'mock-token'),
|
|
||||||
this.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call the callback with a NotFoundError', function () {
|
it('should reject with a NotFoundError', async function () {
|
||||||
return this.callback
|
await expect(
|
||||||
.calledWith(sinon.match.instanceOf(Errors.NotFoundError))
|
this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
|
||||||
.should.equal(true)
|
this.req,
|
||||||
|
(this.token = 'mock-token')
|
||||||
|
)
|
||||||
|
).to.be.rejectedWith(Errors.NotFoundError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with no user_id in the token', function () {
|
describe('with no user_id in the token', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.OneTimeTokenHandler.getValueFromTokenAndExpire = sinon
|
this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.yields(null, { email: this.email })
|
.resolves({ data: { email: this.email } })
|
||||||
return this.UserEmailsConfirmationHandler.confirmEmailFromToken(
|
|
||||||
(this.token = 'mock-token'),
|
|
||||||
this.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call the callback with a NotFoundError', function () {
|
it('should reject with a NotFoundError', async function () {
|
||||||
return this.callback
|
await expect(
|
||||||
.calledWith(sinon.match.instanceOf(Errors.NotFoundError))
|
this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
|
||||||
.should.equal(true)
|
this.req,
|
||||||
|
(this.token = 'mock-token')
|
||||||
|
)
|
||||||
|
).to.be.rejectedWith(Errors.NotFoundError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with no email in the token', function () {
|
describe('with no email in the token', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.OneTimeTokenHandler.getValueFromTokenAndExpire = sinon
|
this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.yields(null, { user_id: this.user_id })
|
.resolves({ data: { user_id: this.user_id } })
|
||||||
return this.UserEmailsConfirmationHandler.confirmEmailFromToken(
|
|
||||||
(this.token = 'mock-token'),
|
|
||||||
this.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call the callback with a NotFoundError', function () {
|
it('should reject with a NotFoundError', async function () {
|
||||||
return this.callback
|
await expect(
|
||||||
.calledWith(sinon.match.instanceOf(Errors.NotFoundError))
|
this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
|
||||||
.should.equal(true)
|
this.req,
|
||||||
|
(this.token = 'mock-token')
|
||||||
|
)
|
||||||
|
).to.be.rejectedWith(Errors.NotFoundError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with no user found', function () {
|
describe('with no user found', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.UserGetter.getUser.yields(null, null)
|
this.UserGetter.promises.getUser.resolves(null)
|
||||||
return this.UserEmailsConfirmationHandler.confirmEmailFromToken(
|
|
||||||
(this.token = 'mock-token'),
|
|
||||||
this.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call the callback with a NotFoundError', function () {
|
it('should reject with a NotFoundError', async function () {
|
||||||
return this.callback
|
await expect(
|
||||||
.calledWith(sinon.match.instanceOf(Errors.NotFoundError))
|
this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
|
||||||
.should.equal(true)
|
this.req,
|
||||||
|
(this.token = 'mock-token')
|
||||||
|
)
|
||||||
|
).to.be.rejectedWith(Errors.NotFoundError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with secondary email missing on user', function () {
|
describe('with secondary email missing on user', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.OneTimeTokenHandler.getValueFromTokenAndExpire = sinon
|
this.OneTimeTokenHandler.promises.peekValueFromToken = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.yields(null, { user_id: this.user_id, email: 'deleted@email.com' })
|
.resolves({
|
||||||
return this.UserEmailsConfirmationHandler.confirmEmailFromToken(
|
data: { user_id: this.user_id, email: 'deleted@email.com' },
|
||||||
(this.token = 'mock-token'),
|
})
|
||||||
this.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call the callback with a NotFoundError', function () {
|
it('should reject with a NotFoundError', async function () {
|
||||||
return this.callback
|
await expect(
|
||||||
.calledWith(sinon.match.instanceOf(Errors.NotFoundError))
|
this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
|
||||||
.should.equal(true)
|
this.req,
|
||||||
|
(this.token = 'mock-token')
|
||||||
|
)
|
||||||
|
).to.be.rejectedWith(Errors.NotFoundError)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when the logged in user is not the token user', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.SessionManager.getLoggedInUserId = sinon
|
||||||
|
.stub()
|
||||||
|
.returns('other-user-id')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reject with a ForbiddenError', async function () {
|
||||||
|
await expect(
|
||||||
|
this.UserEmailsConfirmationHandler.promises.confirmEmailFromToken(
|
||||||
|
this.req,
|
||||||
|
(this.token = 'mock-token')
|
||||||
|
)
|
||||||
|
).to.be.rejectedWith(Errors.ForbiddenError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -440,7 +440,7 @@ describe('UserEmailsController', function () {
|
||||||
|
|
||||||
it('should confirm the email from the token', function () {
|
it('should confirm the email from the token', function () {
|
||||||
this.UserEmailsConfirmationHandler.confirmEmailFromToken
|
this.UserEmailsConfirmationHandler.confirmEmailFromToken
|
||||||
.calledWith(this.token)
|
.calledWith(this.req, this.token)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue