Merge pull request #1383 from sharelatex/spd-rate-limits

Add rate limits to email-related endpoints

GitOrigin-RevId: 05a8b40eb65a55aba35788e2401e6988b672b389
This commit is contained in:
Simon Detheridge 2019-01-11 14:14:27 +00:00 committed by sharelatex
parent eb5738886a
commit 4360a55fdc
8 changed files with 46 additions and 11 deletions

View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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) ->) ->

View file

@ -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

View file

@ -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 ->

View file

@ -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

View file

@ -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