mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
8bde496da4
commit
bf7a18db8b
3 changed files with 53 additions and 2 deletions
|
@ -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}`
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
Loading…
Reference in a new issue