diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee index 8b1b481994..de4710e19a 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee @@ -101,11 +101,15 @@ module.exports = CollaboratorsInviteController = inviteId = req.params.invite_id logger.log {projectId, inviteId}, "resending invite" sendingUser = AuthenticationController.getSessionUser(req) - CollaboratorsInviteHandler.resendInvite projectId, sendingUser, inviteId, (err) -> - if err? - logger.err {projectId, inviteId}, "error resending invite" - return next(err) - res.sendStatus(201) + CollaboratorsInviteController._checkRateLimit sendingUser._id, (error, underRateLimit) -> + return next(error) if error? + if !underRateLimit + return res.sendStatus(429) + CollaboratorsInviteHandler.resendInvite projectId, sendingUser, inviteId, (err) -> + if err? + logger.err {projectId, inviteId}, "error resending invite" + return next(err) + res.sendStatus(201) viewInvite: (req, res, next) -> projectId = req.params.Project_id diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee index bc15576396..8bb6d3907d 100644 --- a/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee @@ -21,7 +21,7 @@ module.exports = throttle: 6 RateLimiter.addCount opts, (err, canContinue)-> if !canContinue - return res.send 500, { message: req.i18n.translate("rate_limit_hit_wait")} + return res.send 429, { message: req.i18n.translate("rate_limit_hit_wait")} PasswordResetHandler.generateAndEmailResetToken email, (err, exists)-> if err? res.send 500, {message:err?.message} diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee index c4155b58cb..ff641189e5 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee @@ -3,6 +3,7 @@ SubscriptionController = require('./SubscriptionController') SubscriptionGroupController = require './SubscriptionGroupController' DomainLicenceController = require './DomainLicenceController' TeamInvitesController = require './TeamInvitesController' +RateLimiterMiddlewear = require('../Security/RateLimiterMiddlewear') Settings = require "settings-sharelatex" module.exports = @@ -24,12 +25,25 @@ module.exports = # Team invites webRouter.get '/subscription/invites/:token/', AuthenticationController.requireLogin(), TeamInvitesController.viewInvite - webRouter.put '/subscription/invites/:token/', AuthenticationController.requireLogin(), + webRouter.put '/subscription/invites/:token/', + AuthenticationController.requireLogin(), + RateLimiterMiddlewear.rateLimit({ + endpointName: 'team-invite', + maxRequests: 10 + timeInterval: 60 + }), TeamInvitesController.acceptInvite # Routes to join a domain licence team webRouter.get '/user/subscription/domain/join', AuthenticationController.requireLogin(), DomainLicenceController.join - webRouter.post '/user/subscription/domain/join', AuthenticationController.requireLogin(), DomainLicenceController.createInvite + webRouter.post '/user/subscription/domain/join', + AuthenticationController.requireLogin(), + RateLimiterMiddlewear.rateLimit({ + endpointName: 'join-domain-subscription', + maxRequests: 10 + timeInterval: 60 + }), + DomainLicenceController.createInvite #recurly callback publicApiRouter.post '/user/subscription/callback', SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback diff --git a/services/web/app/coffee/Features/User/UserEmailsConfirmationHandler.coffee b/services/web/app/coffee/Features/User/UserEmailsConfirmationHandler.coffee index dd87570450..dd79a06e4b 100644 --- a/services/web/app/coffee/Features/User/UserEmailsConfirmationHandler.coffee +++ b/services/web/app/coffee/Features/User/UserEmailsConfirmationHandler.coffee @@ -21,6 +21,7 @@ module.exports = UserEmailsConfirmationHandler = emailOptions = to: email confirmEmailUrl: "#{settings.siteUrl}/user/emails/confirm?token=#{token}" + sendingUser_id: user_id EmailHandler.sendEmail emailTemplate, emailOptions, callback confirmEmailFromToken: (token, callback = (error) ->) -> diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 0599801602..9a3b4b090d 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -122,6 +122,11 @@ module.exports = class Router UserEmailsController.confirm webRouter.post '/user/emails/resend_confirmation', AuthenticationController.requireLogin(), + RateLimiterMiddlewear.rateLimit({ + endpointName: "resend-confirmation" + maxRequests: 10 + timeInterval: 60 + }), UserEmailsController.resendConfirmation if Features.hasFeature 'affiliations' @@ -326,8 +331,14 @@ module.exports = class Router # webRouter.post "/beta/opt-in", AuthenticationController.requireLogin(), BetaProgramController.optIn # webRouter.post "/beta/opt-out", AuthenticationController.requireLogin(), BetaProgramController.optOut webRouter.get "/confirm-password", AuthenticationController.requireLogin(), SudoModeController.sudoModePrompt - webRouter.post "/confirm-password", AuthenticationController.requireLogin(), SudoModeController.submitPassword - + webRouter.post "/confirm-password", + AuthenticationController.requireLogin(), + RateLimiterMiddlewear.rateLimit({ + endpointName: "confirm-password" + maxRequests: 10 + timeInterval: 60 + }), + SudoModeController.submitPassword # New "api" endpoints. Started as a way for v1 to call over to v2 (for # long-term features, as opposed to the nominally temporary ones in the diff --git a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee index affef2dc31..69fff44516 100644 --- a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee +++ b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee @@ -570,6 +570,7 @@ describe "CollaboratorsInviteController", -> @res.render = sinon.stub() @res.sendStatus = sinon.stub() @CollaboratorsInviteHandler.resendInvite = sinon.stub().callsArgWith(3, null) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @callback = sinon.stub() @next = sinon.stub() @@ -585,6 +586,9 @@ describe "CollaboratorsInviteController", -> it 'should have called resendInvite', -> @CollaboratorsInviteHandler.resendInvite.callCount.should.equal 1 + it 'should check the rate limit', -> + @CollaboratorsInviteController._checkRateLimit.callCount.should.equal 1 + describe 'when resendInvite produces an error', -> beforeEach -> diff --git a/services/web/test/unit/coffee/PasswordReset/PasswordResetControllerTests.coffee b/services/web/test/unit/coffee/PasswordReset/PasswordResetControllerTests.coffee index fd118e9c88..377cd0884d 100644 --- a/services/web/test/unit/coffee/PasswordReset/PasswordResetControllerTests.coffee +++ b/services/web/test/unit/coffee/PasswordReset/PasswordResetControllerTests.coffee @@ -51,7 +51,7 @@ describe "PasswordResetController", -> @PasswordResetHandler.generateAndEmailResetToken.callsArgWith(1, null, true) @RateLimiter.addCount.callsArgWith(1, null, false) @res.send = (code)=> - code.should.equal 500 + code.should.equal 429 @PasswordResetHandler.generateAndEmailResetToken.calledWith(@email.trim()).should.equal false done() @PasswordResetController.requestReset @req, @res diff --git a/services/web/test/unit/coffee/User/UserEmailsConfirmationHandlerTests.coffee b/services/web/test/unit/coffee/User/UserEmailsConfirmationHandlerTests.coffee index 95f72e0907..0ab84cda7c 100644 --- a/services/web/test/unit/coffee/User/UserEmailsConfirmationHandlerTests.coffee +++ b/services/web/test/unit/coffee/User/UserEmailsConfirmationHandlerTests.coffee @@ -46,6 +46,7 @@ describe "UserEmailsConfirmationHandler", -> .calledWith('confirmEmail', { to: @email, confirmEmailUrl: 'emails.example.com/user/emails/confirm?token=new-token' + sendingUser_id: @user_id }) .should.equal true