Răsfoiți Sursa

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

[web] Send email notification on account deletion

GitOrigin-RevId: 03c0effba0ee3b829f5b4fe377fe67d05776ba3f
roo hutton 2 ani în urmă
părinte
comite
bf7a18db8b

+ 27 - 2
services/web/app/src/Features/Email/EmailBuilder.js

@@ -527,8 +527,8 @@ templates.groupSSOReauthenticate = ctaTemplate({
     return [
       `Hi,
       <div>
-      Single sign-on for your Overleaf group has been updated. 
-      This means you need to reauthenticate your Overleaf account with your group’s SSO provider. 
+      Single sign-on for your Overleaf group has been updated.
+      This means you need to reauthenticate your Overleaf account with your group’s SSO provider.
       </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({
   subject(opts) {
     return `Overleaf security note: ${opts.action}`

+ 9 - 0
services/web/app/src/Features/User/UserDeleter.js

@@ -16,6 +16,7 @@ const InstitutionsAPI = require('../Institutions/InstitutionsAPI')
 const Modules = require('../../infrastructure/Modules')
 const Errors = require('../Errors/Errors')
 const OnboardingDataCollectionManager = require('../OnboardingDataCollection/OnboardingDataCollectionManager')
+const EmailHandler = require('../Email/EmailHandler')
 
 module.exports = {
   deleteUser: callbackify(deleteUser),
@@ -48,6 +49,7 @@ async function deleteUser(userId, options) {
     await Modules.promises.hooks.fire('deleteUser', userId)
     await _createDeletedUser(user, options)
     await ProjectDeleter.promises.deleteUsersProjects(user._id)
+    await _sendDeleteEmail(user)
     await deleteMongoUser(user._id)
   } catch (error) {
     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) {
   await DeletedUser.updateOne(
     { 'deleterData.deletedUserId': user._id },

+ 17 - 0
services/web/test/unit/src/User/UserDeleterTests.js

@@ -100,6 +100,12 @@ describe('UserDeleter', function () {
       deleteOnboardingDataCollection: sinon.stub().resolves(),
     }
 
+    this.EmailHandler = {
+      promises: {
+        sendEmail: sinon.stub().resolves(),
+      },
+    }
+
     this.UserDeleter = SandboxedModule.require(modulePath, {
       requires: {
         '../../models/User': { User },
@@ -119,6 +125,7 @@ describe('UserDeleter', function () {
         '../../infrastructure/Modules': this.Modules,
         '../OnboardingDataCollection/OnboardingDataCollectionManager':
           this.OnboardingDataCollectionManager,
+        '../Email/EmailHandler': this.EmailHandler,
       },
     })
   })
@@ -251,6 +258,16 @@ describe('UserDeleter', function () {
             await this.UserDeleter.promises.deleteUser(this.userId, {})
             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 () {