Merge pull request #17730 from overleaf/rh-acct-delete-email

[web] Send email notification on account deletion

GitOrigin-RevId: 03c0effba0ee3b829f5b4fe377fe67d05776ba3f
This commit is contained in:
roo hutton 2024-04-09 13:15:46 +01:00 committed by Copybot
parent 8bde496da4
commit bf7a18db8b
3 changed files with 53 additions and 2 deletions

View file

@ -786,6 +786,31 @@ templates.userOnboardingEmail = NoCTAEmailTemplate({
}, },
}) })
templates.deletedAccount = NoCTAEmailTemplate({
subject() {
return 'Overleaf security note: account deletion confirmation'
},
title() {
return 'Account deleted'
},
message(opts, isPlainText) {
const dateFormatted = moment().format('dddd D MMMM YYYY')
const timeFormatted = moment().format('HH:mm')
const helpLink = EmailMessageHelper.displayLink(
'quick guide',
`${settings.siteUrl}/learn/how-to/Keeping_your_account_secure`,
isPlainText
)
return [
`We are writing to let you know that your ${settings.appName} account was deleted on ${dateFormatted} at ${timeFormatted} GMT.`,
`If this was you, you're all set and can ignore this email.`,
`If you did not take this action, please get in touch with our support team at ${settings.adminEmail} to report this as potentially suspicious activity on your account.`,
`For tips on keeping your ${settings.appName} account secure, read our ${helpLink}.`,
]
},
})
templates.securityAlert = NoCTAEmailTemplate({ templates.securityAlert = NoCTAEmailTemplate({
subject(opts) { subject(opts) {
return `Overleaf security note: ${opts.action}` return `Overleaf security note: ${opts.action}`

View file

@ -16,6 +16,7 @@ const InstitutionsAPI = require('../Institutions/InstitutionsAPI')
const Modules = require('../../infrastructure/Modules') const Modules = require('../../infrastructure/Modules')
const Errors = require('../Errors/Errors') const Errors = require('../Errors/Errors')
const OnboardingDataCollectionManager = require('../OnboardingDataCollection/OnboardingDataCollectionManager') const OnboardingDataCollectionManager = require('../OnboardingDataCollection/OnboardingDataCollectionManager')
const EmailHandler = require('../Email/EmailHandler')
module.exports = { module.exports = {
deleteUser: callbackify(deleteUser), deleteUser: callbackify(deleteUser),
@ -48,6 +49,7 @@ async function deleteUser(userId, options) {
await Modules.promises.hooks.fire('deleteUser', userId) await Modules.promises.hooks.fire('deleteUser', userId)
await _createDeletedUser(user, options) await _createDeletedUser(user, options)
await ProjectDeleter.promises.deleteUsersProjects(user._id) await ProjectDeleter.promises.deleteUsersProjects(user._id)
await _sendDeleteEmail(user)
await deleteMongoUser(user._id) await deleteMongoUser(user._id)
} catch (error) { } catch (error) {
logger.warn({ error, userId }, 'something went wrong deleting the user') logger.warn({ error, userId }, 'something went wrong deleting the user')
@ -110,6 +112,13 @@ async function ensureCanDeleteUser(user) {
} }
} }
async function _sendDeleteEmail(user) {
const emailOptions = {
to: user.email,
}
await EmailHandler.promises.sendEmail('deletedAccount', emailOptions)
}
async function _createDeletedUser(user, options) { async function _createDeletedUser(user, options) {
await DeletedUser.updateOne( await DeletedUser.updateOne(
{ 'deleterData.deletedUserId': user._id }, { 'deleterData.deletedUserId': user._id },

View file

@ -100,6 +100,12 @@ describe('UserDeleter', function () {
deleteOnboardingDataCollection: sinon.stub().resolves(), deleteOnboardingDataCollection: sinon.stub().resolves(),
} }
this.EmailHandler = {
promises: {
sendEmail: sinon.stub().resolves(),
},
}
this.UserDeleter = SandboxedModule.require(modulePath, { this.UserDeleter = SandboxedModule.require(modulePath, {
requires: { requires: {
'../../models/User': { User }, '../../models/User': { User },
@ -119,6 +125,7 @@ describe('UserDeleter', function () {
'../../infrastructure/Modules': this.Modules, '../../infrastructure/Modules': this.Modules,
'../OnboardingDataCollection/OnboardingDataCollectionManager': '../OnboardingDataCollection/OnboardingDataCollectionManager':
this.OnboardingDataCollectionManager, this.OnboardingDataCollectionManager,
'../Email/EmailHandler': this.EmailHandler,
}, },
}) })
}) })
@ -251,6 +258,16 @@ describe('UserDeleter', function () {
await this.UserDeleter.promises.deleteUser(this.userId, {}) await this.UserDeleter.promises.deleteUser(this.userId, {})
this.DeletedUserMock.verify() this.DeletedUserMock.verify()
}) })
it('should email the user', async function () {
await this.UserDeleter.promises.deleteUser(this.userId, {})
const emailOptions = {
to: 'bob@bob.com',
}
expect(
this.EmailHandler.promises.sendEmail
).to.have.been.calledWith('deletedAccount', emailOptions)
})
}) })
describe('when unsubscribing from mailchimp fails', function () { describe('when unsubscribing from mailchimp fails', function () {