mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #3909 from overleaf/jel-reconfirm-email-template
Add reconfirm email template GitOrigin-RevId: 2488c79c25a7148f601e3e3e2021cdbee4be7b4c
This commit is contained in:
parent
78326fb352
commit
4f8a905e9b
7 changed files with 255 additions and 98 deletions
|
@ -283,6 +283,32 @@ templates.projectInvite = ctaTemplate({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
templates.reconfirmEmail = ctaTemplate({
|
||||||
|
subject() {
|
||||||
|
return `Reconfirm Email - ${settings.appName}`
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return 'Reconfirm Email'
|
||||||
|
},
|
||||||
|
message(opts) {
|
||||||
|
return [
|
||||||
|
`Please reconfirm your email address, ${opts.to}, on your ${settings.appName} account.`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
secondaryMessage() {
|
||||||
|
return [
|
||||||
|
'If you did not request this, you can simply ignore this message.',
|
||||||
|
`If you have any questions or trouble confirming your email address, please get in touch with our support team at ${settings.adminEmail}.`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
ctaText() {
|
||||||
|
return 'Reconfirm Email'
|
||||||
|
},
|
||||||
|
ctaURL(opts) {
|
||||||
|
return opts.confirmEmailUrl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
templates.verifyEmailToJoinTeam = ctaTemplate({
|
templates.verifyEmailToJoinTeam = ctaTemplate({
|
||||||
subject(opts) {
|
subject(opts) {
|
||||||
return `${_.escape(
|
return `${_.escape(
|
||||||
|
|
|
@ -1,31 +1,17 @@
|
||||||
/* eslint-disable
|
|
||||||
node/handle-callback-err,
|
|
||||||
max-len,
|
|
||||||
no-unused-vars,
|
|
||||||
*/
|
|
||||||
// TODO: This file was created by bulk-decaffeinate.
|
|
||||||
// Fix any style issues and re-enable lint.
|
|
||||||
/*
|
|
||||||
* decaffeinate suggestions:
|
|
||||||
* DS102: Remove unnecessary code created because of implicit returns
|
|
||||||
* DS207: Consider shorter variations of null checks
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
const Settings = require('settings-sharelatex')
|
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const logger = require('logger-sharelatex')
|
|
||||||
const { db } = require('../../infrastructure/mongodb')
|
const { db } = require('../../infrastructure/mongodb')
|
||||||
const Errors = require('../Errors/Errors')
|
const Errors = require('../Errors/Errors')
|
||||||
|
const { promisifyAll } = require('../../util/promises')
|
||||||
|
|
||||||
const ONE_HOUR_IN_S = 60 * 60
|
const ONE_HOUR_IN_S = 60 * 60
|
||||||
|
|
||||||
module.exports = {
|
const OneTimeTokenHandler = {
|
||||||
getNewToken(use, data, options, callback) {
|
getNewToken(use, data, options, callback) {
|
||||||
// options is optional
|
// options is optional
|
||||||
if (options == null) {
|
if (!options) {
|
||||||
options = {}
|
options = {}
|
||||||
}
|
}
|
||||||
if (callback == null) {
|
if (!callback) {
|
||||||
callback = function (error, data) {}
|
callback = function (error, data) {}
|
||||||
}
|
}
|
||||||
if (typeof options === 'function') {
|
if (typeof options === 'function') {
|
||||||
|
@ -36,7 +22,7 @@ module.exports = {
|
||||||
const createdAt = new Date()
|
const createdAt = new Date()
|
||||||
const expiresAt = new Date(createdAt.getTime() + expiresIn * 1000)
|
const expiresAt = new Date(createdAt.getTime() + expiresIn * 1000)
|
||||||
const token = crypto.randomBytes(32).toString('hex')
|
const token = crypto.randomBytes(32).toString('hex')
|
||||||
return db.tokens.insertOne(
|
db.tokens.insertOne(
|
||||||
{
|
{
|
||||||
use,
|
use,
|
||||||
token,
|
token,
|
||||||
|
@ -45,20 +31,20 @@ module.exports = {
|
||||||
expiresAt
|
expiresAt
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
if (error != null) {
|
if (error) {
|
||||||
return callback(error)
|
return callback(error)
|
||||||
}
|
}
|
||||||
return callback(null, token)
|
callback(null, token)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
getValueFromTokenAndExpire(use, token, callback) {
|
getValueFromTokenAndExpire(use, token, callback) {
|
||||||
if (callback == null) {
|
if (!callback) {
|
||||||
callback = function (error, data) {}
|
callback = function (error, data) {}
|
||||||
}
|
}
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
return db.tokens.findOneAndUpdate(
|
db.tokens.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
use,
|
use,
|
||||||
token,
|
token,
|
||||||
|
@ -71,15 +57,19 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function (error, result) {
|
function (error, result) {
|
||||||
if (error != null) {
|
if (error) {
|
||||||
return callback(error)
|
return callback(error)
|
||||||
}
|
}
|
||||||
const token = result.value
|
const token = result.value
|
||||||
if (token == null) {
|
if (!token) {
|
||||||
return callback(new Errors.NotFoundError('no token found'))
|
return callback(new Errors.NotFoundError('no token found'))
|
||||||
}
|
}
|
||||||
return callback(null, token.data)
|
callback(null, token.data)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OneTimeTokenHandler.promises = promisifyAll(OneTimeTokenHandler)
|
||||||
|
|
||||||
|
module.exports = OneTimeTokenHandler
|
||||||
|
|
|
@ -5,10 +5,11 @@ const settings = require('settings-sharelatex')
|
||||||
const Errors = require('../Errors/Errors')
|
const Errors = require('../Errors/Errors')
|
||||||
const UserUpdater = require('./UserUpdater')
|
const UserUpdater = require('./UserUpdater')
|
||||||
const UserGetter = require('./UserGetter')
|
const UserGetter = require('./UserGetter')
|
||||||
const { promisify } = require('util')
|
const { callbackify, promisify } = require('util')
|
||||||
|
|
||||||
// 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
|
||||||
|
const TOKEN_USE = 'email_confirmation'
|
||||||
|
|
||||||
function sendConfirmationEmail(userId, email, emailTemplate, callback) {
|
function sendConfirmationEmail(userId, email, emailTemplate, callback) {
|
||||||
if (arguments.length === 3) {
|
if (arguments.length === 3) {
|
||||||
|
@ -28,7 +29,7 @@ function sendConfirmationEmail(userId, email, emailTemplate, callback) {
|
||||||
}
|
}
|
||||||
const data = { user_id: userId, email }
|
const data = { user_id: userId, email }
|
||||||
OneTimeTokenHandler.getNewToken(
|
OneTimeTokenHandler.getNewToken(
|
||||||
'email_confirmation',
|
TOKEN_USE,
|
||||||
data,
|
data,
|
||||||
{ expiresIn: TOKEN_EXPIRY_IN_S },
|
{ expiresIn: TOKEN_EXPIRY_IN_S },
|
||||||
function (err, token) {
|
function (err, token) {
|
||||||
|
@ -45,12 +46,36 @@ function sendConfirmationEmail(userId, email, emailTemplate, callback) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendReconfirmationEmail(userId, email) {
|
||||||
|
email = EmailHelper.parseEmail(email)
|
||||||
|
if (!email) {
|
||||||
|
throw new Error('invalid email')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = { user_id: userId, email }
|
||||||
|
const token = await OneTimeTokenHandler.promises.getNewToken(
|
||||||
|
TOKEN_USE,
|
||||||
|
data,
|
||||||
|
{ expiresIn: TOKEN_EXPIRY_IN_S }
|
||||||
|
)
|
||||||
|
|
||||||
|
const emailOptions = {
|
||||||
|
to: email,
|
||||||
|
confirmEmailUrl: `${settings.siteUrl}/user/emails/confirm?token=${token}`,
|
||||||
|
sendingUser_id: userId
|
||||||
|
}
|
||||||
|
|
||||||
|
await EmailHandler.promises.sendEmail('reconfirmEmail', emailOptions)
|
||||||
|
}
|
||||||
|
|
||||||
const UserEmailsConfirmationHandler = {
|
const UserEmailsConfirmationHandler = {
|
||||||
sendConfirmationEmail,
|
sendConfirmationEmail,
|
||||||
|
|
||||||
|
sendReconfirmationEmail: callbackify(sendReconfirmationEmail),
|
||||||
|
|
||||||
confirmEmailFromToken(token, callback) {
|
confirmEmailFromToken(token, callback) {
|
||||||
OneTimeTokenHandler.getValueFromTokenAndExpire(
|
OneTimeTokenHandler.getValueFromTokenAndExpire(
|
||||||
'email_confirmation',
|
TOKEN_USE,
|
||||||
token,
|
token,
|
||||||
function (error, data) {
|
function (error, data) {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
@ -87,6 +87,32 @@ function resendConfirmation(req, res, next) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendReconfirmation(req, res, next) {
|
||||||
|
const userId = AuthenticationController.getLoggedInUserId(req)
|
||||||
|
const email = EmailHelper.parseEmail(req.body.email)
|
||||||
|
if (!email) {
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
UserGetter.getUserByAnyEmail(email, { _id: 1 }, function (error, user) {
|
||||||
|
if (error) {
|
||||||
|
return next(error)
|
||||||
|
}
|
||||||
|
if (!user || user._id.toString() !== userId) {
|
||||||
|
return res.sendStatus(422)
|
||||||
|
}
|
||||||
|
UserEmailsConfirmationHandler.sendReconfirmationEmail(
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
function (error) {
|
||||||
|
if (error) {
|
||||||
|
return next(error)
|
||||||
|
}
|
||||||
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const UserEmailsController = {
|
const UserEmailsController = {
|
||||||
list(req, res, next) {
|
list(req, res, next) {
|
||||||
const userId = AuthenticationController.getLoggedInUserId(req)
|
const userId = AuthenticationController.getLoggedInUserId(req)
|
||||||
|
@ -176,6 +202,8 @@ const UserEmailsController = {
|
||||||
|
|
||||||
resendConfirmation,
|
resendConfirmation,
|
||||||
|
|
||||||
|
sendReconfirmation,
|
||||||
|
|
||||||
showConfirm(req, res, next) {
|
showConfirm(req, res, next) {
|
||||||
res.render('user/confirm_email', {
|
res.render('user/confirm_email', {
|
||||||
token: req.query.token,
|
token: req.query.token,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import getMeta from '../../../utils/meta'
|
||||||
|
|
||||||
export default App.controller(
|
export default App.controller(
|
||||||
'UserAffiliationsReconfirmController',
|
'UserAffiliationsReconfirmController',
|
||||||
function ($scope, UserAffiliationsDataService, $window) {
|
function ($scope, $http, $window) {
|
||||||
const samlInitPath = ExposedSettings.samlInitPath
|
const samlInitPath = ExposedSettings.samlInitPath
|
||||||
$scope.reconfirm = {}
|
$scope.reconfirm = {}
|
||||||
$scope.ui = $scope.ui || {} // $scope.ui inherited on settings page
|
$scope.ui = $scope.ui || {} // $scope.ui inherited on settings page
|
||||||
|
@ -30,7 +30,11 @@ export default App.controller(
|
||||||
function sendReconfirmEmail(email) {
|
function sendReconfirmEmail(email) {
|
||||||
$scope.ui.hasError = false
|
$scope.ui.hasError = false
|
||||||
$scope.ui.isMakingRequest = true
|
$scope.ui.isMakingRequest = true
|
||||||
UserAffiliationsDataService.resendConfirmationEmail(email)
|
$http
|
||||||
|
.post('/user/emails/send-reconfirmation', {
|
||||||
|
email,
|
||||||
|
_csrf: window.csrfToken
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
$scope.reconfirm[email].reconfirmationSent = true
|
$scope.reconfirm[email].reconfirmationSent = true
|
||||||
})
|
})
|
||||||
|
|
|
@ -307,6 +307,43 @@ describe('EmailBuilder', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('reconfirmEmail', function () {
|
||||||
|
before(function () {
|
||||||
|
this.emailAddress = 'example@overleaf.com'
|
||||||
|
this.userId = 'abc123'
|
||||||
|
this.opts = {
|
||||||
|
to: this.emailAddress,
|
||||||
|
confirmEmailUrl: `${this.settings.siteUrl}/user/emails/confirm?token=aToken123`,
|
||||||
|
sendingUser_id: this.userId
|
||||||
|
}
|
||||||
|
this.email = this.EmailBuilder.buildEmail('reconfirmEmail', this.opts)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should build the email', function () {
|
||||||
|
expect(this.email.html).to.exist
|
||||||
|
expect(this.email.text).to.exist
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('HTML email', function () {
|
||||||
|
it('should include a CTA button and a fallback CTA link', function () {
|
||||||
|
const dom = cheerio.load(this.email.html)
|
||||||
|
const buttonLink = dom('a:contains("Reconfirm Email")')
|
||||||
|
expect(buttonLink.length).to.equal(1)
|
||||||
|
expect(buttonLink.attr('href')).to.equal(this.opts.confirmEmailUrl)
|
||||||
|
const fallback = dom('.force-overleaf-style').last()
|
||||||
|
expect(fallback.length).to.equal(1)
|
||||||
|
const fallbackLink = fallback.html()
|
||||||
|
expect(fallbackLink).to.contain(this.opts.confirmEmailUrl)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('plain text email', function () {
|
||||||
|
it('should contain the CTA link', function () {
|
||||||
|
expect(this.email.text).to.contain(this.opts.confirmEmailUrl)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('verifyEmailToJoinTeam', function () {
|
describe('verifyEmailToJoinTeam', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
this.emailAddress = 'example@overleaf.com'
|
this.emailAddress = 'example@overleaf.com'
|
||||||
|
|
|
@ -18,6 +18,7 @@ describe('UserEmailsController', function () {
|
||||||
|
|
||||||
this.UserGetter = {
|
this.UserGetter = {
|
||||||
getUserFullEmails: sinon.stub(),
|
getUserFullEmails: sinon.stub(),
|
||||||
|
getUserByAnyEmail: sinon.stub(),
|
||||||
promises: {
|
promises: {
|
||||||
getUser: sinon.stub().resolves(this.user)
|
getUser: sinon.stub().resolves(this.user)
|
||||||
}
|
}
|
||||||
|
@ -45,13 +46,7 @@ describe('UserEmailsController', function () {
|
||||||
this.EmailHelper = { parseEmail: sinon.stub() }
|
this.EmailHelper = { parseEmail: sinon.stub() }
|
||||||
this.endorseAffiliation = sinon.stub().yields()
|
this.endorseAffiliation = sinon.stub().yields()
|
||||||
this.InstitutionsAPI = {
|
this.InstitutionsAPI = {
|
||||||
endorseAffiliation: this.endorseAffiliation,
|
endorseAffiliation: this.endorseAffiliation
|
||||||
getInstitutionViaDomain: sinon
|
|
||||||
.stub()
|
|
||||||
.withArgs('overleaf.com')
|
|
||||||
.resolves({ sso_enabled: true })
|
|
||||||
.withArgs('example.com')
|
|
||||||
.resolves({ sso_enabled: false })
|
|
||||||
}
|
}
|
||||||
this.HttpErrorHandler = { conflict: sinon.stub() }
|
this.HttpErrorHandler = { conflict: sinon.stub() }
|
||||||
this.UserEmailsController = SandboxedModule.require(modulePath, {
|
this.UserEmailsController = SandboxedModule.require(modulePath, {
|
||||||
|
@ -69,6 +64,7 @@ describe('UserEmailsController', function () {
|
||||||
}),
|
}),
|
||||||
'../Helpers/EmailHelper': this.EmailHelper,
|
'../Helpers/EmailHelper': this.EmailHelper,
|
||||||
'./UserEmailsConfirmationHandler': (this.UserEmailsConfirmationHandler = {
|
'./UserEmailsConfirmationHandler': (this.UserEmailsConfirmationHandler = {
|
||||||
|
sendReconfirmationEmail: sinon.stub(),
|
||||||
promises: {
|
promises: {
|
||||||
sendConfirmationEmail: sinon.stub().resolves()
|
sendConfirmationEmail: sinon.stub().resolves()
|
||||||
}
|
}
|
||||||
|
@ -439,8 +435,13 @@ describe('UserEmailsController', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('resendConfirmation', function () {
|
describe('resendConfirmation', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
this.EmailHelper.parseEmail.returnsArg(0)
|
||||||
|
this.UserGetter.getUserByAnyEmail.yields(undefined, {
|
||||||
|
_id: this.user._id
|
||||||
|
})
|
||||||
this.req = {
|
this.req = {
|
||||||
body: {}
|
body: {}
|
||||||
}
|
}
|
||||||
|
@ -452,74 +453,120 @@ describe('UserEmailsController', function () {
|
||||||
.stub()
|
.stub()
|
||||||
.yields()
|
.yields()
|
||||||
})
|
})
|
||||||
describe('when institution SSO is released', function () {
|
|
||||||
|
it('should send the email', function (done) {
|
||||||
|
this.req = {
|
||||||
|
body: {
|
||||||
|
email: 'test@example.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.UserEmailsController.sendReconfirmation(
|
||||||
|
this.req,
|
||||||
|
this.res,
|
||||||
|
this.next
|
||||||
|
)
|
||||||
|
expect(this.UserEmailsConfirmationHandler.sendReconfirmationEmail).to.have
|
||||||
|
.been.calledOnce
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 422 if email not valid', function (done) {
|
||||||
|
this.req = {
|
||||||
|
body: {}
|
||||||
|
}
|
||||||
|
this.UserEmailsController.resendConfirmation(
|
||||||
|
this.req,
|
||||||
|
this.res,
|
||||||
|
this.next
|
||||||
|
)
|
||||||
|
expect(this.UserEmailsConfirmationHandler.sendConfirmationEmail).to.not
|
||||||
|
.have.been.called
|
||||||
|
expect(this.res.sendStatus.lastCall.args[0]).to.equal(422)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
describe('email on another user account', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.Features.hasFeature.withArgs('saml').returns(true)
|
this.UserGetter.getUserByAnyEmail.yields(undefined, {
|
||||||
})
|
_id: 'another-user-id'
|
||||||
describe('for an institution SSO email', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
this.req.body.email = 'with-sso@overleaf.com'
|
|
||||||
})
|
|
||||||
it('should not send the email', function () {
|
|
||||||
this.UserEmailsController.resendConfirmation(
|
|
||||||
this.req,
|
|
||||||
this.res,
|
|
||||||
() => {
|
|
||||||
this.UserEmailsConfirmationHandler.sendConfirmationEmail.should
|
|
||||||
.not.have.been.called.once
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('for a non-institution SSO email', function () {
|
it('should return 422', function (done) {
|
||||||
beforeEach(function () {
|
this.req = {
|
||||||
this.req.body.email = 'without-sso@example.com'
|
body: {
|
||||||
})
|
email: 'test@example.com'
|
||||||
it('should send the email', function () {
|
}
|
||||||
this.UserEmailsController.resendConfirmation(
|
}
|
||||||
this.req,
|
this.UserEmailsController.resendConfirmation(
|
||||||
this.res,
|
this.req,
|
||||||
() => {
|
this.res,
|
||||||
this.UserEmailsConfirmationHandler.sendConfirmationEmail.should
|
this.next
|
||||||
.have.been.called.once
|
)
|
||||||
}
|
expect(this.UserEmailsConfirmationHandler.sendConfirmationEmail).to.not
|
||||||
)
|
.have.been.called
|
||||||
})
|
expect(this.res.sendStatus.lastCall.args[0]).to.equal(422)
|
||||||
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('when institution SSO is not released', function () {
|
})
|
||||||
|
|
||||||
|
describe('sendReconfirmation', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.res.sendStatus = sinon.stub()
|
||||||
|
this.UserGetter.getUserByAnyEmail.yields(undefined, {
|
||||||
|
_id: this.user._id
|
||||||
|
})
|
||||||
|
this.EmailHelper.parseEmail.returnsArg(0)
|
||||||
|
})
|
||||||
|
it('should send the email', function (done) {
|
||||||
|
this.req = {
|
||||||
|
body: {
|
||||||
|
email: 'test@example.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.UserEmailsController.sendReconfirmation(
|
||||||
|
this.req,
|
||||||
|
this.res,
|
||||||
|
this.next
|
||||||
|
)
|
||||||
|
expect(this.UserEmailsConfirmationHandler.sendReconfirmationEmail).to.have
|
||||||
|
.been.calledOnce
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
it('should return 400 if email not valid', function (done) {
|
||||||
|
this.req = {
|
||||||
|
body: {}
|
||||||
|
}
|
||||||
|
this.UserEmailsController.sendReconfirmation(
|
||||||
|
this.req,
|
||||||
|
this.res,
|
||||||
|
this.next
|
||||||
|
)
|
||||||
|
expect(this.UserEmailsConfirmationHandler.sendReconfirmationEmail).to.not
|
||||||
|
.have.been.called
|
||||||
|
expect(this.res.sendStatus.lastCall.args[0]).to.equal(400)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
describe('email on another user account', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.Features.hasFeature.withArgs('saml').returns(false)
|
this.UserGetter.getUserByAnyEmail.yields(undefined, {
|
||||||
})
|
_id: 'another-user-id'
|
||||||
describe('for an institution SSO email', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
this.req.body.email = 'with-sso@overleaf.com'
|
|
||||||
})
|
|
||||||
it('should send the email', function () {
|
|
||||||
this.UserEmailsController.resendConfirmation(
|
|
||||||
this.req,
|
|
||||||
this.res,
|
|
||||||
() => {
|
|
||||||
this.UserEmailsConfirmationHandler.sendConfirmationEmail.should
|
|
||||||
.have.been.called.once
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('for a non-institution SSO email', function () {
|
it('should return 422', function (done) {
|
||||||
beforeEach(function () {
|
this.req = {
|
||||||
this.req.body.email = 'without-sso@example.com'
|
body: {
|
||||||
})
|
email: 'test@example.com'
|
||||||
it('should send the email', function () {
|
}
|
||||||
this.UserEmailsController.resendConfirmation(
|
}
|
||||||
this.req,
|
this.UserEmailsController.sendReconfirmation(
|
||||||
this.res,
|
this.req,
|
||||||
() => {
|
this.res,
|
||||||
this.UserEmailsConfirmationHandler.sendConfirmationEmail.should
|
this.next
|
||||||
.have.been.called.once
|
)
|
||||||
}
|
expect(this.UserEmailsConfirmationHandler.sendReconfirmationEmail).to
|
||||||
)
|
.not.have.been.called
|
||||||
})
|
expect(this.res.sendStatus.lastCall.args[0]).to.equal(422)
|
||||||
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue