From f8e643570c4f49f880fcd2e00c193b93dc44dec0 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Tue, 29 Jul 2025 14:54:09 +0200 Subject: [PATCH] [web] Remove the endpoint `/user/emails` (POST) (#27418) * Remove `/user/emails` (post) * Update test GitOrigin-RevId: 3979820935209ca36fdd8fabc016ad55d4858cef --- .../src/Features/User/UserEmailsController.js | 52 ------ services/web/app/src/router.mjs | 10 +- .../src/User/UserEmailsControllerTests.js | 162 ------------------ 3 files changed, 1 insertion(+), 223 deletions(-) diff --git a/services/web/app/src/Features/User/UserEmailsController.js b/services/web/app/src/Features/User/UserEmailsController.js index 2f9be5427e..7f235cead3 100644 --- a/services/web/app/src/Features/User/UserEmailsController.js +++ b/services/web/app/src/Features/User/UserEmailsController.js @@ -54,55 +54,6 @@ async function _sendSecurityAlertEmail(user, email) { await EmailHandler.promises.sendEmail('securityAlert', emailOptions) } -/** - * This method is for adding a secondary email to be confirmed via an emailed link. - * For code confirmation, see the `addWithConfirmationCode` method in this file. - */ -async function add(req, res, next) { - const userId = SessionManager.getLoggedInUserId(req.session) - const email = EmailHelper.parseEmail(req.body.email) - if (!email) { - return res.sendStatus(422) - } - const user = await UserGetter.promises.getUser(userId, { - email: 1, - 'emails.email': 1, - }) - - if (user.emails.length >= Settings.emailAddressLimit) { - return res.status(422).json({ message: 'secondary email limit exceeded' }) - } - - const affiliationOptions = { - university: req.body.university, - role: req.body.role, - department: req.body.department, - } - - try { - await UserUpdater.promises.addEmailAddress( - userId, - email, - affiliationOptions, - { - initiatorId: user._id, - ipAddress: req.ip, - } - ) - } catch (error) { - return UserEmailsController._handleEmailError(error, req, res, next) - } - - await _sendSecurityAlertEmail(user, email) - - await UserEmailsConfirmationHandler.promises.sendConfirmationEmail( - userId, - email - ) - - res.sendStatus(204) -} - async function resendConfirmation(req, res) { const userId = SessionManager.getLoggedInUserId(req.session) const email = EmailHelper.parseEmail(req.body.email) @@ -160,7 +111,6 @@ async function sendExistingEmailConfirmationCode(req, res) { /** * This method is for adding a secondary email to be confirmed via a code. - * For email link confirmation see the `add` method in this file. */ async function addWithConfirmationCode(req, res) { delete req.session.pendingSecondaryEmail @@ -666,8 +616,6 @@ const UserEmailsController = { }) }, - add: expressify(add), - addWithConfirmationCode: expressify(addWithConfirmationCode), checkNewSecondaryEmailConfirmationCode: expressify( diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs index e727fa7bc5..558d1e572e 100644 --- a/services/web/app/src/router.mjs +++ b/services/web/app/src/router.mjs @@ -67,6 +67,7 @@ import { plainTextResponse } from './infrastructure/Response.js' import PublicAccessLevels from './Features/Authorization/PublicAccessLevels.js' import SocketDiagnostics from './Features/SocketDiagnostics/SocketDiagnostics.mjs' import ClsiCacheController from './Features/Compile/ClsiCacheController.js' + const ClsiCookieManager = ClsiCookieManagerFactory( Settings.apis.clsi != null ? Settings.apis.clsi.backendGroupName : undefined ) @@ -384,15 +385,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { ) if (Features.hasFeature('affiliations')) { - webRouter.post( - '/user/emails', - AuthenticationController.requireLogin(), - PermissionsController.requirePermission('add-secondary-email'), - RateLimiterMiddleware.rateLimit(rateLimiters.addEmail), - CaptchaMiddleware.validateCaptcha('addEmail'), - UserEmailsController.add - ) - webRouter.post( '/user/emails/delete', AuthenticationController.requireLogin(), diff --git a/services/web/test/unit/src/User/UserEmailsControllerTests.js b/services/web/test/unit/src/User/UserEmailsControllerTests.js index 2cd7ad491f..e64629e88a 100644 --- a/services/web/test/unit/src/User/UserEmailsControllerTests.js +++ b/services/web/test/unit/src/User/UserEmailsControllerTests.js @@ -122,168 +122,6 @@ describe('UserEmailsController', function () { }) }) - describe('Add', function () { - beforeEach(function () { - this.newEmail = 'new_email@baz.com' - this.req.body = { - email: this.newEmail, - university: { name: 'University Name' }, - department: 'Department', - role: 'Role', - } - this.EmailHelper.parseEmail.returns(this.newEmail) - this.UserEmailsConfirmationHandler.sendConfirmationEmail = sinon - .stub() - .yields() - }) - - it('passed audit log to addEmailAddress', function (done) { - this.res.sendStatus = sinon.stub() - this.res.sendStatus.callsFake(() => { - const addCall = this.UserUpdater.promises.addEmailAddress.lastCall - expect(addCall.args[3]).to.deep.equal({ - initiatorId: this.user._id, - ipAddress: this.req.ip, - }) - done() - }) - this.UserEmailsController.add(this.req, this.res) - }) - - it('adds new email', function (done) { - this.UserEmailsController.add( - this.req, - { - sendStatus: code => { - code.should.equal(204) - assertCalledWith(this.EmailHelper.parseEmail, this.newEmail) - assertCalledWith( - this.UserUpdater.promises.addEmailAddress, - this.user._id, - this.newEmail - ) - - const affiliationOptions = - this.UserUpdater.promises.addEmailAddress.lastCall.args[2] - Object.keys(affiliationOptions).length.should.equal(3) - affiliationOptions.university.should.equal(this.req.body.university) - affiliationOptions.department.should.equal(this.req.body.department) - affiliationOptions.role.should.equal(this.req.body.role) - - done() - }, - }, - this.next - ) - }) - - it('sends a security alert email', function (done) { - this.res.sendStatus = sinon.stub() - this.res.sendStatus.callsFake(() => { - const emailCall = this.EmailHandler.promises.sendEmail.getCall(0) - emailCall.args[0].should.to.equal('securityAlert') - emailCall.args[1].to.should.equal(this.user.email) - emailCall.args[1].actionDescribed.should.contain( - 'a secondary email address' - ) - emailCall.args[1].to.should.equal(this.user.email) - emailCall.args[1].message[0].should.contain(this.newEmail) - done() - }) - - this.UserEmailsController.add(this.req, this.res) - }) - - it('sends an email confirmation', function (done) { - this.UserEmailsController.add( - this.req, - { - sendStatus: code => { - code.should.equal(204) - assertCalledWith( - this.UserEmailsConfirmationHandler.promises.sendConfirmationEmail, - this.user._id, - this.newEmail - ) - done() - }, - }, - this.next - ) - }) - - it('handles email parse error', function (done) { - this.EmailHelper.parseEmail.returns(null) - this.UserEmailsController.add( - this.req, - { - sendStatus: code => { - code.should.equal(422) - assertNotCalled(this.UserUpdater.promises.addEmailAddress) - done() - }, - }, - this.next - ) - }) - - it('should pass the error to the next handler when adding the email fails', function (done) { - this.UserUpdater.promises.addEmailAddress.rejects(new Error()) - this.UserEmailsController.add(this.req, this.res, error => { - expect(error).to.be.instanceof(Error) - done() - }) - }) - - it('should call the HTTP conflict handler when the email already exists', function (done) { - this.UserUpdater.promises.addEmailAddress.rejects( - new Errors.EmailExistsError() - ) - this.HttpErrorHandler.conflict = sinon.spy((req, res, message) => { - req.should.exist - res.should.exist - message.should.equal('email_already_registered') - done() - }) - this.UserEmailsController.add(this.req, this.res, this.next) - }) - - it("should call the HTTP conflict handler when there's a domain matching error", function (done) { - this.UserUpdater.promises.addEmailAddress.rejects( - new Error('422: Email does not belong to university') - ) - this.HttpErrorHandler.conflict = sinon.spy((req, res, message) => { - req.should.exist - res.should.exist - message.should.equal('email_does_not_belong_to_university') - done() - }) - this.UserEmailsController.add(this.req, this.res, this.next) - }) - - it('should fail to add new emails when the limit has been reached', function (done) { - this.user.emails = [] - for (let i = 0; i < 10; i++) { - this.user.emails.push({ email: `example${i}@overleaf.com` }) - } - this.UserEmailsController.add( - this.req, - { - status: code => { - expect(code).to.equal(422) - return { - json: error => { - expect(error.message).to.equal('secondary email limit exceeded') - done() - }, - } - }, - }, - this.next - ) - }) - }) - describe('addWithConfirmationCode', function () { beforeEach(function () { this.newEmail = 'new_email@baz.com'