From 3cf436c89ed591f9483910593475ad54fa6c06d9 Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Fri, 23 May 2025 12:45:12 +0200 Subject: [PATCH] Merge pull request #25886 from overleaf/msm-add-skip-email-to-delete-user [CE] Add `--skip-email` to `delete-user` script GitOrigin-RevId: d0f5ced26930060df1e9f40dee97839076743bbd --- .../web/app/src/Features/User/UserDeleter.js | 8 ++++++-- .../server-ce-scripts/scripts/delete-user.mjs | 20 +++++++++++++++++-- .../test/unit/src/User/UserDeleterTests.js | 9 +++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/services/web/app/src/Features/User/UserDeleter.js b/services/web/app/src/Features/User/UserDeleter.js index 721943b163..662c51ca65 100644 --- a/services/web/app/src/Features/User/UserDeleter.js +++ b/services/web/app/src/Features/User/UserDeleter.js @@ -60,8 +60,12 @@ async function deleteUser(userId, options) { await _createDeletedUser(user, options) logger.info({ userId }, 'deleting user projects') await ProjectDeleter.promises.deleteUsersProjects(user._id) - logger.info({ userId }, 'sending deletion email to user') - await _sendDeleteEmail(user, options.force) + if (options.skipEmail) { + logger.info({ userId }, 'skipping sending deletion email to user') + } else { + logger.info({ userId }, 'sending deletion email to user') + await _sendDeleteEmail(user, options.force) + } logger.info({ userId }, 'deleting user record') await deleteMongoUser(user._id) logger.info({ userId }, 'user deletion complete') diff --git a/services/web/modules/server-ce-scripts/scripts/delete-user.mjs b/services/web/modules/server-ce-scripts/scripts/delete-user.mjs index 9b7b4592a3..1cb8aebdef 100644 --- a/services/web/modules/server-ce-scripts/scripts/delete-user.mjs +++ b/services/web/modules/server-ce-scripts/scripts/delete-user.mjs @@ -1,13 +1,28 @@ import UserGetter from '../../../app/src/Features/User/UserGetter.js' import UserDeleter from '../../../app/src/Features/User/UserDeleter.js' import { fileURLToPath } from 'url' +import minimist from 'minimist' const filename = fileURLToPath(import.meta.url) async function main() { - const email = (process.argv.slice(2).pop() || '').replace(/^--email=/, '') + const argv = minimist(process.argv.slice(2), { + string: ['email'], + boolean: ['skip-email'], + }) + + const { email, 'skip-email': skipEmail } = argv if (!email) { - console.error(`Usage: node ${filename} --email=joe@example.com`) + console.error( + `Usage: node ${filename} [--skip-email] --email=joe@example.com + +Deletes a user. All users' projects will also be deleted. + +Options: + --email email address of the user being deleted + --skip-email (optional) when present, the user is not notified of the deletion via email +` + ) process.exit(1) } @@ -25,6 +40,7 @@ async function main() { const options = { ipAddress: '0.0.0.0', force: true, + skipEmail, } UserDeleter.deleteUser(user._id, options, function (err) { if (err) { diff --git a/services/web/test/unit/src/User/UserDeleterTests.js b/services/web/test/unit/src/User/UserDeleterTests.js index 7ffaaede55..0c5e00c0f5 100644 --- a/services/web/test/unit/src/User/UserDeleterTests.js +++ b/services/web/test/unit/src/User/UserDeleterTests.js @@ -314,6 +314,15 @@ describe('UserDeleter', function () { ).to.have.been.calledWith('securityAlert', emailOptions) }) + it('should not email the user with skipEmail === true', async function () { + await this.UserDeleter.promises.deleteUser(this.userId, { + ipAddress: this.ipAddress, + skipEmail: true, + }) + expect(this.EmailHandler.promises.sendEmail).not.to.have.been + .called + }) + it('should fail when the email service fails', async function () { this.EmailHandler.promises.sendEmail = sinon .stub()