diff --git a/services/web/app/src/Features/Email/EmailBuilder.js b/services/web/app/src/Features/Email/EmailBuilder.js
index 8192debdc4..afb8a9ab08 100644
--- a/services/web/app/src/Features/Email/EmailBuilder.js
+++ b/services/web/app/src/Features/Email/EmailBuilder.js
@@ -603,10 +603,22 @@ templates.securityAlert = NoCTAEmailTemplate({
`${settings.siteUrl}/learn/how-to/Keeping_your_account_secure`,
isPlainText
)
+
+ const actionDescribed = EmailMessageHelper.cleanHTML(
+ opts.actionDescribed,
+ isPlainText
+ )
+
+ if (!opts.message) {
+ opts.message = []
+ }
+ const message = opts.message.map(m => {
+ return EmailMessageHelper.cleanHTML(m, isPlainText)
+ })
+
return [
- `We are writing to let you know that ${
- opts.actionDescribed
- } on ${dateFormatted} at ${timeFormatted} GMT.`,
+ `We are writing to let you know that ${actionDescribed} on ${dateFormatted} at ${timeFormatted} GMT.`,
+ ...message,
`If this was you, you can ignore this email.`,
`If this was not you, we recommend getting in touch with our support team at ${
settings.adminEmail
diff --git a/services/web/app/src/Features/Email/EmailMessageHelper.js b/services/web/app/src/Features/Email/EmailMessageHelper.js
index f0cccacde7..9b608c3d27 100644
--- a/services/web/app/src/Features/Email/EmailMessageHelper.js
+++ b/services/web/app/src/Features/Email/EmailMessageHelper.js
@@ -1,7 +1,27 @@
+const sanitizeHtml = require('sanitize-html')
+const sanitizeOptions = {
+ html: {
+ allowedTags: ['span', 'b', 'br', 'i'],
+ allowedAttributes: {
+ span: ['style', 'class']
+ }
+ },
+ plainText: {
+ allowedTags: [],
+ allowedAttributes: {}
+ }
+}
+
+function cleanHTML(text, isPlainText) {
+ if (!isPlainText) return sanitizeHtml(text, sanitizeOptions.html)
+ return sanitizeHtml(text, sanitizeOptions.plainText)
+}
+
function displayLink(text, url, isPlainText) {
return isPlainText ? `${text} (${url})` : `${text}`
}
module.exports = {
+ cleanHTML,
displayLink
}
diff --git a/services/web/app/src/Features/User/UserEmailsConfirmationHandler.js b/services/web/app/src/Features/User/UserEmailsConfirmationHandler.js
index 3f6c38dd23..e69eff4fc6 100644
--- a/services/web/app/src/Features/User/UserEmailsConfirmationHandler.js
+++ b/services/web/app/src/Features/User/UserEmailsConfirmationHandler.js
@@ -5,46 +5,49 @@ const settings = require('settings-sharelatex')
const Errors = require('../Errors/Errors')
const UserUpdater = require('./UserUpdater')
const UserGetter = require('./UserGetter')
+const { promisify } = require('util')
const ONE_YEAR_IN_S = 365 * 24 * 60 * 60
-const UserEmailsConfirmationHandler = {
- sendConfirmationEmail(userId, email, emailTemplate, callback) {
- if (arguments.length === 3) {
- callback = emailTemplate
- emailTemplate = 'confirmEmail'
- }
+function sendConfirmationEmail(userId, email, emailTemplate, callback) {
+ if (arguments.length === 3) {
+ callback = emailTemplate
+ emailTemplate = 'confirmEmail'
+ }
- // when force-migrating accounts to v2 from v1, we don't want to send confirmation messages -
- // setting this env var allows us to turn this behaviour off
- if (process.env['SHARELATEX_NO_CONFIRMATION_MESSAGES'] != null) {
- return callback(null)
- }
+ // when force-migrating accounts to v2 from v1, we don't want to send confirmation messages -
+ // setting this env var allows us to turn this behaviour off
+ if (process.env['SHARELATEX_NO_CONFIRMATION_MESSAGES'] != null) {
+ return callback(null)
+ }
- email = EmailHelper.parseEmail(email)
- if (!email) {
- return callback(new Error('invalid email'))
- }
- const data = { user_id: userId, email }
- OneTimeTokenHandler.getNewToken(
- 'email_confirmation',
- data,
- { expiresIn: ONE_YEAR_IN_S },
- function(err, token) {
- if (err) {
- return callback(err)
- }
- const emailOptions = {
- to: email,
- confirmEmailUrl: `${
- settings.siteUrl
- }/user/emails/confirm?token=${token}`,
- sendingUser_id: userId
- }
- EmailHandler.sendEmail(emailTemplate, emailOptions, callback)
+ email = EmailHelper.parseEmail(email)
+ if (!email) {
+ return callback(new Error('invalid email'))
+ }
+ const data = { user_id: userId, email }
+ OneTimeTokenHandler.getNewToken(
+ 'email_confirmation',
+ data,
+ { expiresIn: ONE_YEAR_IN_S },
+ function(err, token) {
+ if (err) {
+ return callback(err)
}
- )
- },
+ const emailOptions = {
+ to: email,
+ confirmEmailUrl: `${
+ settings.siteUrl
+ }/user/emails/confirm?token=${token}`,
+ sendingUser_id: userId
+ }
+ EmailHandler.sendEmail(emailTemplate, emailOptions, callback)
+ }
+ )
+}
+
+const UserEmailsConfirmationHandler = {
+ sendConfirmationEmail,
confirmEmailFromToken(token, callback) {
OneTimeTokenHandler.getValueFromTokenAndExpire(
@@ -83,4 +86,8 @@ const UserEmailsConfirmationHandler = {
}
}
+UserEmailsConfirmationHandler.promises = {
+ sendConfirmationEmail: promisify(sendConfirmationEmail)
+}
+
module.exports = UserEmailsConfirmationHandler
diff --git a/services/web/app/src/Features/User/UserEmailsController.js b/services/web/app/src/Features/User/UserEmailsController.js
index b278985247..ef9933f85a 100644
--- a/services/web/app/src/Features/User/UserEmailsController.js
+++ b/services/web/app/src/Features/User/UserEmailsController.js
@@ -1,40 +1,55 @@
-let UserEmailsController
const AuthenticationController = require('../Authentication/AuthenticationController')
const UserGetter = require('./UserGetter')
const UserUpdater = require('./UserUpdater')
+const EmailHandler = require('../Email/EmailHandler')
const EmailHelper = require('../Helpers/EmailHelper')
const UserEmailsConfirmationHandler = require('./UserEmailsConfirmationHandler')
const { endorseAffiliation } = require('../Institutions/InstitutionsAPI')
const Errors = require('../Errors/Errors')
const HttpErrorHandler = require('../Errors/HttpErrorHandler')
+const { expressify } = require('../../util/promises')
-function add(req, res, next) {
+async function add(req, res, next) {
const userId = AuthenticationController.getLoggedInUserId(req)
const email = EmailHelper.parseEmail(req.body.email)
if (!email) {
return res.sendStatus(422)
}
+ const user = await UserGetter.promises.getUser(userId, { email: 1 })
const affiliationOptions = {
university: req.body.university,
role: req.body.role,
department: req.body.department
}
- UserUpdater.addEmailAddress(userId, email, affiliationOptions, function(
- error
- ) {
- if (error) {
- return UserEmailsController._handleEmailError(error, req, res, next)
- }
- UserEmailsConfirmationHandler.sendConfirmationEmail(userId, email, function(
- error
- ) {
- if (error) {
- return next(error)
- }
- res.sendStatus(204)
- })
- })
+
+ try {
+ await UserUpdater.promises.addEmailAddress(
+ userId,
+ email,
+ affiliationOptions
+ )
+ } catch (error) {
+ return UserEmailsController._handleEmailError(error, req, res, next)
+ }
+
+ const emailOptions = {
+ to: user.email,
+ actionDescribed: `a secondary email address has been added to your account ${
+ user.email
+ }`,
+ message: [
+ `Added:
${email}`
+ ],
+ action: 'secondary email address added'
+ }
+ await EmailHandler.promises.sendEmail('securityAlert', emailOptions)
+ await UserEmailsConfirmationHandler.promises.sendConfirmationEmail(
+ userId,
+ email
+ )
+
+ res.sendStatus(204)
}
function resendConfirmation(req, res, next) {
@@ -61,7 +76,7 @@ function resendConfirmation(req, res, next) {
})
}
-module.exports = UserEmailsController = {
+const UserEmailsController = {
list(req, res, next) {
const userId = AuthenticationController.getLoggedInUserId(req)
UserGetter.getUserFullEmails(userId, function(error, fullEmails) {
@@ -72,7 +87,7 @@ module.exports = UserEmailsController = {
})
},
- add,
+ add: expressify(add),
remove(req, res, next) {
const userId = AuthenticationController.getLoggedInUserId(req)
@@ -169,3 +184,9 @@ module.exports = UserEmailsController = {
next(error)
}
}
+
+UserEmailsController.promises = {
+ add
+}
+
+module.exports = UserEmailsController
diff --git a/services/web/test/unit/src/Email/EmailBuilderTests.js b/services/web/test/unit/src/Email/EmailBuilderTests.js
index 004472b5b8..e3c64c4d0a 100644
--- a/services/web/test/unit/src/Email/EmailBuilderTests.js
+++ b/services/web/test/unit/src/Email/EmailBuilderTests.js
@@ -104,13 +104,25 @@ describe('EmailBuilder', function() {
describe('templates', function() {
describe('securityAlert', function() {
before(function() {
- this.email = 'example@overleaf.com'
+ this.message = 'more details about the action'
+ this.messageHTML = `
${
+ this.message
+ }`
+ this.messageNotAllowedHTML = `