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

@ -527,8 +527,8 @@ templates.groupSSOReauthenticate = ctaTemplate({
return [ return [
`Hi, `Hi,
<div> <div>
Single sign-on for your Overleaf group has been updated. Single sign-on for your Overleaf group has been updated.
This means you need to reauthenticate your Overleaf account with your groups SSO provider. This means you need to reauthenticate your Overleaf account with your groups SSO provider.
</div> </div>
`, `,
] ]
@ -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 () {