[web] When switching primary email, delete the old primary if it's unconfirmed (#23688)

* Add note to ConfirmModal: unconfirmed primary will be deleted

* Change confirm button copy

* Promisify `UserEmailsController.setDefault`

* Update tests after promisification

* Delete unconfirmed primary when swapped

* Fixup apostrophe in translation

* `npm run extract-translations`

* Add unit tests

* Add acceptance tests

* Fix frontend tests

* Make email address bold

* Add "We removed the previous primary..." to the email

GitOrigin-RevId: c971e219e36e509f9963e1720acdd44f562a05b5
This commit is contained in:
Antoine Clausse
2025-02-21 09:05:44 +00:00
committed by Copybot
parent f0d94f06ed
commit 34cac93f9a
11 changed files with 195 additions and 70 deletions
@@ -544,6 +544,73 @@ async function remove(req, res) {
res.sendStatus(200)
}
async function setDefault(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
const email = EmailHelper.parseEmail(req.body.email)
if (!email) {
return res.sendStatus(422)
}
const { emails, email: oldDefault } = await UserGetter.promises.getUser(
userId,
{ email: 1, emails: 1 }
)
const primaryEmailData = emails?.find(email => email.email === oldDefault)
const deleteOldEmail =
req.query['delete-unconfirmed-primary'] !== undefined &&
primaryEmailData &&
!primaryEmailData.confirmedAt
const auditLog = {
initiatorId: userId,
ipAddress: req.ip,
}
try {
await UserUpdater.promises.setDefaultEmailAddress(
userId,
email,
false,
auditLog,
true,
deleteOldEmail
)
} catch (err) {
return UserEmailsController._handleEmailError(err, req, res, next)
}
SessionManager.setInSessionUser(req.session, { email })
const user = SessionManager.getSessionUser(req.session)
try {
await UserSessionsManager.promises.removeSessionsFromRedis(
user,
req.sessionID // remove all sessions except the current session
)
} catch (err) {
logger.warn(
{ err },
'failed revoking secondary sessions after changing default email'
)
}
if (
req.query['delete-unconfirmed-primary'] !== undefined &&
primaryEmailData &&
!primaryEmailData.confirmedAt
) {
await UserUpdater.promises.removeEmailAddress(
userId,
primaryEmailData.email,
{
initiatorId: userId,
ipAddress: req.ip,
extraInfo: {
info: 'removed unconfirmed email after setting new primary',
},
}
)
}
res.sendStatus(200)
}
const UserEmailsController = {
list(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
@@ -566,43 +633,7 @@ const UserEmailsController = {
remove: expressify(remove),
setDefault(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
const email = EmailHelper.parseEmail(req.body.email)
if (!email) {
return res.sendStatus(422)
}
const auditLog = {
initiatorId: userId,
ipAddress: req.ip,
}
UserUpdater.setDefaultEmailAddress(
userId,
email,
false,
auditLog,
true,
err => {
if (err) {
return UserEmailsController._handleEmailError(err, req, res, next)
}
SessionManager.setInSessionUser(req.session, { email })
const user = SessionManager.getSessionUser(req.session)
UserSessionsManager.removeSessionsFromRedis(
user,
req.sessionID, // remove all sessions except the current session
err => {
if (err)
logger.warn(
{ err },
'failed revoking secondary sessions after changing default email'
)
}
)
res.sendStatus(200)
}
)
},
setDefault: expressify(setDefault),
endorse(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
@@ -20,7 +20,13 @@ const _ = require('lodash')
const Modules = require('../../infrastructure/Modules')
const UserSessionsManager = require('./UserSessionsManager')
async function _sendSecurityAlertPrimaryEmailChanged(userId, oldEmail, email) {
async function _sendSecurityAlertPrimaryEmailChanged(
userId,
oldEmail,
email,
deleteOldEmail
) {
// here
// Send email to the following:
// - the old primary
// - the new primary
@@ -31,6 +37,11 @@ async function _sendSecurityAlertPrimaryEmailChanged(userId, oldEmail, email) {
const emailOptions = {
actionDescribed: `the primary email address on your account was changed to ${email}`,
action: 'change of primary email address',
message: deleteOldEmail
? [
`We also removed the previous primary email ${oldEmail} from the account.`,
]
: [],
}
async function sendToRecipients(recipients) {
@@ -161,7 +172,8 @@ async function setDefaultEmailAddress(
email,
allowUnconfirmed,
auditLog,
sendSecurityAlert
sendSecurityAlert,
deleteOldEmail = false
) {
email = EmailHelper.parseEmail(email)
if (email == null) {
@@ -212,11 +224,14 @@ async function setDefaultEmailAddress(
if (sendSecurityAlert) {
// no need to wait, errors are logged and not passed back
_sendSecurityAlertPrimaryEmailChanged(userId, oldEmail, email).catch(
err => {
logger.error({ err }, 'failed to send security alert email')
}
)
_sendSecurityAlertPrimaryEmailChanged(
userId,
oldEmail,
email,
deleteOldEmail
).catch(err => {
logger.error({ err }, 'failed to send security alert email')
})
}
try {