Refactor rate limiting code around sending invites

This commit is contained in:
James Allen 2017-01-27 16:26:58 +01:00
parent 68aab6e05c
commit b086d7afa2
7 changed files with 149 additions and 101 deletions

View file

@ -23,7 +23,7 @@ module.exports = CollaboratorsInviteController =
return next(err) return next(err)
res.json({invites: invites}) res.json({invites: invites})
_checkShouldInviteEmail: (sendingUser, email, callback=(err, shouldAllowInvite)->) -> _checkShouldInviteEmail: (email, callback=(err, shouldAllowInvite)->) ->
if Settings.restrictInvitesToExistingAccounts == true if Settings.restrictInvitesToExistingAccounts == true
logger.log {email}, "checking if user exists with this email" logger.log {email}, "checking if user exists with this email"
UserGetter.getUser {email: email}, {_id: 1}, (err, user) -> UserGetter.getUser {email: email}, {_id: 1}, (err, user) ->
@ -31,19 +31,20 @@ module.exports = CollaboratorsInviteController =
userExists = user? and user?._id? userExists = user? and user?._id?
callback(null, userExists) callback(null, userExists)
else else
UserGetter.getUser sendingUser._id, {features:1, _id:1}, (err, user)-> callback(null, true)
if err?
return callback(err) _checkRateLimit: (user_id, callback = (error) ->) ->
collabLimit = user?.features?.collaborators || 1 LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)->
if collabLimit == -1 return callback(err) if err?
collabLimit = 20 if collabLimit == -1
collabLimit = collabLimit * 10 collabLimit = 20
opts = collabLimit = collabLimit * 10
endpointName: "invite_to_project" opts =
timeInterval: 60 * 30 endpointName: "invite-to-project-by-user-id"
subjectName: sendingUser._id timeInterval: 60 * 30
throttle: collabLimit subjectName: user_id
rateLimiter.addCount opts, callback throttle: collabLimit
rateLimiter.addCount opts, callback
inviteToProject: (req, res, next) -> inviteToProject: (req, res, next) ->
projectId = req.params.Project_id projectId = req.params.Project_id
@ -64,20 +65,24 @@ module.exports = CollaboratorsInviteController =
if !email? or email == "" if !email? or email == ""
logger.log {projectId, email, sendingUserId}, "invalid email address" logger.log {projectId, email, sendingUserId}, "invalid email address"
return res.sendStatus(400) return res.sendStatus(400)
CollaboratorsInviteController._checkShouldInviteEmail sendingUser, email, (err, shouldAllowInvite)-> CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) ->
if err? return next(error) if error?
logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address" if !underRateLimit
return next(err) return res.sendStatus(429)
if !shouldAllowInvite CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)->
logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address"
return res.json {invite: null, error: 'cannot_invite_non_user'}
CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) ->
if err? if err?
logger.err {projectId, email, sendingUserId}, "error creating project invite" logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address"
return next(err) return next(err)
logger.log {projectId, email, sendingUserId}, "invite created" if !shouldAllowInvite
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true}) logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address"
return res.json {invite: invite} return res.json {invite: null, error: 'cannot_invite_non_user'}
CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) ->
if err?
logger.err {projectId, email, sendingUserId}, "error creating project invite"
return next(err)
logger.log {projectId, email, sendingUserId}, "invite created"
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true})
return res.json {invite: invite}
revokeInvite: (req, res, next) -> revokeInvite: (req, res, next) ->
projectId = req.params.Project_id projectId = req.params.Project_id

View file

@ -80,7 +80,7 @@ module.exports = CollaboratorsInviteHandler =
# Send email and notification in background # Send email and notification in background
CollaboratorsInviteHandler._sendMessages projectId, sendingUser, invite, (err) -> CollaboratorsInviteHandler._sendMessages projectId, sendingUser, invite, (err) ->
if err? if err?
logger.err {projectId, email}, "error sending messages for invite" logger.err {err, projectId, email}, "error sending messages for invite"
callback(null, invite) callback(null, invite)

View file

@ -22,13 +22,13 @@ module.exports =
webRouter.post( webRouter.post(
'/project/:Project_id/invite', '/project/:Project_id/invite',
RateLimiterMiddlewear.rateLimit({ RateLimiterMiddlewear.rateLimit({
endpointName: "invite-to-project" endpointName: "invite-to-project-by-project-id"
params: ["Project_id"] params: ["Project_id"]
maxRequests: 100 maxRequests: 100
timeInterval: 60 * 10 timeInterval: 60 * 10
}), }),
RateLimiterMiddlewear.rateLimit({ RateLimiterMiddlewear.rateLimit({
endpointName: "invite-to-project-ip" endpointName: "invite-to-project-by-ip"
ipOnly:true ipOnly:true
maxRequests: 100 maxRequests: 100
timeInterval: 60 * 10 timeInterval: 60 * 10

View file

@ -97,7 +97,7 @@ Thank you
templates.projectInvite = templates.projectInvite =
subject: _.template "<%= project.name.slice(0, 40) %> - shared by <%= owner.email %>" subject: _.template "<%= project.name %> - shared by <%= owner.email %>"
layout: BaseWithHeaderEmailLayout layout: BaseWithHeaderEmailLayout
type:"notification" type:"notification"
plainTextTemplate: _.template """ plainTextTemplate: _.template """
@ -111,16 +111,16 @@ Thank you
""" """
compiledTemplate: (opts) -> compiledTemplate: (opts) ->
SingleCTAEmailBody({ SingleCTAEmailBody({
title: "#{ opts.project.name.slice(0, 40) } &ndash; shared by #{ opts.owner.email }" title: "#{ opts.project.name } &ndash; shared by #{ opts.owner.email }"
greeting: "Hi," greeting: "Hi,"
message: "#{ opts.owner.email } wants to share &ldquo;#{ opts.project.name.slice(0, 40) }&rdquo; with you." message: "#{ opts.owner.email } wants to share &ldquo;#{ opts.project.name }&rdquo; with you."
secondaryMessage: null secondaryMessage: null
ctaText: "View project" ctaText: "View project"
ctaURL: opts.inviteUrl ctaURL: opts.inviteUrl
gmailGoToAction: gmailGoToAction:
target: opts.inviteUrl target: opts.inviteUrl
name: "View project" name: "View project"
description: "Join #{ opts.project.name.slice(0, 40) } at ShareLaTeX" description: "Join #{ opts.project.name } at ShareLaTeX"
}) })
templates.completeJoinGroupAccount = templates.completeJoinGroupAccount =

View file

@ -1,20 +1,25 @@
logger = require("logger-sharelatex") logger = require("logger-sharelatex")
Project = require("../../models/Project").Project Project = require("../../models/Project").Project
User = require("../../models/User").User UserGetter = require("../User/UserGetter")
SubscriptionLocator = require("./SubscriptionLocator") SubscriptionLocator = require("./SubscriptionLocator")
Settings = require("settings-sharelatex") Settings = require("settings-sharelatex")
CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler") CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler")
CollaboratorsInvitesHandler = require("../Collaborators/CollaboratorsInviteHandler") CollaboratorsInvitesHandler = require("../Collaborators/CollaboratorsInviteHandler")
module.exports = module.exports =
allowedNumberOfCollaboratorsInProject: (project_id, callback) -> allowedNumberOfCollaboratorsInProject: (project_id, callback) ->
getOwnerOfProject project_id, (error, owner)-> Project.findById project_id, 'owner_ref', (error, project) =>
return callback(error) if error? return callback(error) if error?
if owner.features? and owner.features.collaborators? @allowedNumberOfCollaboratorsForUser project.owner_ref, callback
callback null, owner.features.collaborators
allowedNumberOfCollaboratorsForUser: (user_id, callback) ->
UserGetter.getUser user_id, {features: 1}, (error, user) ->
return callback(error) if error?
if user.features? and user.features.collaborators?
callback null, user.features.collaborators
else else
callback null, Settings.defaultPlanCode.collaborators callback null, Settings.defaultPlanCode.collaborators
canAddXCollaborators: (project_id, x_collaborators, callback = (error, allowed)->) -> canAddXCollaborators: (project_id, x_collaborators, callback = (error, allowed)->) ->
@allowedNumberOfCollaboratorsInProject project_id, (error, allowed_number) => @allowedNumberOfCollaboratorsInProject project_id, (error, allowed_number) =>
@ -63,8 +68,4 @@ module.exports =
logger.log user_id:user_id, limitReached:limitReached, currentTotal: subscription.member_ids.length, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached" logger.log user_id:user_id, limitReached:limitReached, currentTotal: subscription.member_ids.length, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached"
callback(err, limitReached, subscription) callback(err, limitReached, subscription)
getOwnerOfProject = (project_id, callback)-> getOwnerIdOfProject = (project_id, callback)->
Project.findById project_id, 'owner_ref', (error, project) ->
return callback(error) if error?
User.findById project.owner_ref, (error, owner) ->
callback(error, owner)

View file

@ -114,7 +114,8 @@ describe "CollaboratorsInviteController", ->
describe 'when all goes well', -> describe 'when all goes well', ->
beforeEach -> beforeEach ->
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true)
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
@CollaboratorsInviteController.inviteToProject @req, @res, @next @CollaboratorsInviteController.inviteToProject @req, @res, @next
@ -128,7 +129,7 @@ describe "CollaboratorsInviteController", ->
it 'should have called _checkShouldInviteEmail', -> it 'should have called _checkShouldInviteEmail', ->
@CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1
@CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
it 'should have called inviteToProject', -> it 'should have called inviteToProject', ->
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1 @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1
@ -141,7 +142,8 @@ describe "CollaboratorsInviteController", ->
describe 'when the user is not allowed to add more collaborators', -> describe 'when the user is not allowed to add more collaborators', ->
beforeEach -> beforeEach ->
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true)
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, false) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, false)
@CollaboratorsInviteController.inviteToProject @req, @res, @next @CollaboratorsInviteController.inviteToProject @req, @res, @next
@ -159,7 +161,8 @@ describe "CollaboratorsInviteController", ->
describe 'when canAddXCollaborators produces an error', -> describe 'when canAddXCollaborators produces an error', ->
beforeEach -> beforeEach ->
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true)
@err = new Error('woops') @err = new Error('woops')
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, @err) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, @err)
@CollaboratorsInviteController.inviteToProject @req, @res, @next @CollaboratorsInviteController.inviteToProject @req, @res, @next
@ -178,7 +181,8 @@ describe "CollaboratorsInviteController", ->
describe 'when inviteToProject produces an error', -> describe 'when inviteToProject produces an error', ->
beforeEach -> beforeEach ->
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true)
@err = new Error('woops') @err = new Error('woops')
@CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, @err) @CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, @err)
@CollaboratorsInviteController.inviteToProject @req, @res, @next @CollaboratorsInviteController.inviteToProject @req, @res, @next
@ -193,7 +197,7 @@ describe "CollaboratorsInviteController", ->
it 'should have called _checkShouldInviteEmail', -> it 'should have called _checkShouldInviteEmail', ->
@CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1
@CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
it 'should have called inviteToProject', -> it 'should have called inviteToProject', ->
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1 @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1
@ -202,7 +206,8 @@ describe "CollaboratorsInviteController", ->
describe 'when _checkShouldInviteEmail disallows the invite', -> describe 'when _checkShouldInviteEmail disallows the invite', ->
beforeEach -> beforeEach ->
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, false) @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, false)
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true)
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
@CollaboratorsInviteController.inviteToProject @req, @res, @next @CollaboratorsInviteController.inviteToProject @req, @res, @next
@ -212,7 +217,7 @@ describe "CollaboratorsInviteController", ->
it 'should have called _checkShouldInviteEmail', -> it 'should have called _checkShouldInviteEmail', ->
@CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1
@CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
it 'should not have called inviteToProject', -> it 'should not have called inviteToProject', ->
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0 @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0
@ -220,7 +225,8 @@ describe "CollaboratorsInviteController", ->
describe 'when _checkShouldInviteEmail produces an error', -> describe 'when _checkShouldInviteEmail produces an error', ->
beforeEach -> beforeEach ->
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, new Error('woops')) @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, new Error('woops'))
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true)
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
@CollaboratorsInviteController.inviteToProject @req, @res, @next @CollaboratorsInviteController.inviteToProject @req, @res, @next
@ -230,7 +236,7 @@ describe "CollaboratorsInviteController", ->
it 'should have called _checkShouldInviteEmail', -> it 'should have called _checkShouldInviteEmail', ->
@CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1
@CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
it 'should not have called inviteToProject', -> it 'should not have called inviteToProject', ->
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0 @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0
@ -240,7 +246,8 @@ describe "CollaboratorsInviteController", ->
beforeEach -> beforeEach ->
@req.session.user = {_id: 'abc', email: 'me@example.com'} @req.session.user = {_id: 'abc', email: 'me@example.com'}
@req.body.email = 'me@example.com' @req.body.email = 'me@example.com'
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true)
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
@CollaboratorsInviteController.inviteToProject @req, @res, @next @CollaboratorsInviteController.inviteToProject @req, @res, @next
@ -261,6 +268,22 @@ describe "CollaboratorsInviteController", ->
it 'should not have called emitToRoom', -> it 'should not have called emitToRoom', ->
@EditorRealTimeController.emitToRoom.callCount.should.equal 0 @EditorRealTimeController.emitToRoom.callCount.should.equal 0
describe 'when _checkRateLimit returns false', ->
beforeEach ->
@CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, false)
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
@CollaboratorsInviteController.inviteToProject @req, @res, @next
it 'should send a 429 response', ->
@res.sendStatus.calledWith(429).should.equal true
it 'should not call inviteToProject', ->
@CollaboratorsInviteHandler.inviteToProject.called.should.equal false
it 'should not call emitToRoom', ->
@EditorRealTimeController.emitToRoom.called.should.equal false
describe "viewInvite", -> describe "viewInvite", ->
@ -679,13 +702,12 @@ describe "CollaboratorsInviteController", ->
beforeEach -> beforeEach ->
@email = 'user@example.com' @email = 'user@example.com'
describe 'when we should be restricting to existing accounts', -> describe 'when we should be restricting to existing accounts', ->
beforeEach -> beforeEach ->
@settings.restrictInvitesToExistingAccounts = true @settings.restrictInvitesToExistingAccounts = true
@call = (callback) => @call = (callback) =>
@CollaboratorsInviteController._checkShouldInviteEmail {}, @email, callback @CollaboratorsInviteController._checkShouldInviteEmail @email, callback
describe 'when user account is present', -> describe 'when user account is present', ->
@ -730,46 +752,43 @@ describe "CollaboratorsInviteController", ->
expect(shouldAllow).to.equal undefined expect(shouldAllow).to.equal undefined
done() done()
describe 'when we should not be restricting on only registered users but do rate limit', -> describe '_checkRateLimit', ->
beforeEach ->
@settings.restrictInvitesToExistingAccounts = false
@sendingUserId = "32312313"
@LimitationsManager.allowedNumberOfCollaboratorsForUser = sinon.stub()
@LimitationsManager.allowedNumberOfCollaboratorsForUser.withArgs(@sendingUserId).yields(null, 17)
beforeEach -> it 'should callback with `true` when rate limit under', (done) ->
@settings.restrictInvitesToExistingAccounts = false @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
@sendingUser = @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=>
_id:"32312313" @RateLimiter.addCount.called.should.equal true
features: result.should.equal true
collaborators:17.8 done()
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @sendingUser)
it 'should callback with `true` when rate limit under', (done) -> it 'should callback with `false` when rate limit hit', (done) ->
@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, false)
@CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=>
@RateLimiter.addCount.called.should.equal true @RateLimiter.addCount.called.should.equal true
result.should.equal true result.should.equal false
done() done()
it 'should call rate limiter with 10x the collaborators', (done) ->
@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=>
@RateLimiter.addCount.args[0][0].throttle.should.equal(170)
done()
it 'should callback with `false` when rate limit hit', (done) -> it 'should call rate limiter with 200 when collaborators is -1', (done) ->
@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, false) @LimitationsManager.allowedNumberOfCollaboratorsForUser.withArgs(@sendingUserId).yields(null, -1)
@CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
@RateLimiter.addCount.called.should.equal true @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=>
result.should.equal false @RateLimiter.addCount.args[0][0].throttle.should.equal(200)
done() done()
it 'should call rate limiter with 10x the collaborators', (done) ->
@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=>
@RateLimiter.addCount.args[0][0].throttle.should.equal(178)
done()
it 'should call rate limiter with 200 when collaborators is -1', (done) -> it 'should call rate limiter with 10 when user has no collaborators set', (done) ->
@sendingUser.features.collaborators = -1 @LimitationsManager.allowedNumberOfCollaboratorsForUser.withArgs(@sendingUserId).yields(null)
@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=>
@RateLimiter.addCount.args[0][0].throttle.should.equal(200) @RateLimiter.addCount.args[0][0].throttle.should.equal(10)
done() done()
it 'should call rate limiter with 10 when user has no collaborators set', (done) ->
delete @sendingUser.features
@RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
@CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=>
@RateLimiter.addCount.args[0][0].throttle.should.equal(10)
done()

View file

@ -6,17 +6,17 @@ Settings = require("settings-sharelatex")
describe "LimitationsManager", -> describe "LimitationsManager", ->
beforeEach -> beforeEach ->
@project = { _id: "project-id" } @project = { _id: @project_id = "project-id" }
@user = { _id: "user-id", features:{} } @user = { _id: @user_id = "user-id", features:{} }
@Project = @Project =
findById: (project_id, fields, callback) => findById: (project_id, fields, callback) =>
if project_id == @project_id if project_id == @project_id
callback null, @project callback null, @project
else else
callback null, null callback null, null
@User = @UserGetter =
findById: (user_id, callback) => getUser: (user_id, filter, callback) =>
if user_id == @user.id if user_id == @user_id
callback null, @user callback null, @user
else else
callback null, null callback null, null
@ -26,7 +26,7 @@ describe "LimitationsManager", ->
@LimitationsManager = SandboxedModule.require modulePath, requires: @LimitationsManager = SandboxedModule.require modulePath, requires:
'../../models/Project' : Project: @Project '../../models/Project' : Project: @Project
'../../models/User' : User: @User '../User/UserGetter' : @UserGetter
'./SubscriptionLocator':@SubscriptionLocator './SubscriptionLocator':@SubscriptionLocator
'settings-sharelatex' : @Settings = {} 'settings-sharelatex' : @Settings = {}
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {} "../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {}
@ -37,6 +37,7 @@ describe "LimitationsManager", ->
describe "when the project is owned by a user without a subscription", -> describe "when the project is owned by a user without a subscription", ->
beforeEach -> beforeEach ->
@Settings.defaultPlanCode = collaborators: 23 @Settings.defaultPlanCode = collaborators: 23
@project.owner_ref = @user_id
delete @user.features delete @user.features
@callback = sinon.stub() @callback = sinon.stub()
@LimitationsManager.allowedNumberOfCollaboratorsInProject(@project_id, @callback) @LimitationsManager.allowedNumberOfCollaboratorsInProject(@project_id, @callback)
@ -46,6 +47,7 @@ describe "LimitationsManager", ->
describe "when the project is owned by a user with a subscription", -> describe "when the project is owned by a user with a subscription", ->
beforeEach -> beforeEach ->
@project.owner_ref = @user_id
@user.features = @user.features =
collaborators: 21 collaborators: 21
@callback = sinon.stub() @callback = sinon.stub()
@ -53,6 +55,27 @@ describe "LimitationsManager", ->
it "should return the number of collaborators the user is allowed", -> it "should return the number of collaborators the user is allowed", ->
@callback.calledWith(null, @user.features.collaborators).should.equal true @callback.calledWith(null, @user.features.collaborators).should.equal true
describe "allowedNumberOfCollaboratorsForUser", ->
describe "when the user has no features", ->
beforeEach ->
@Settings.defaultPlanCode = collaborators: 23
delete @user.features
@callback = sinon.stub()
@LimitationsManager.allowedNumberOfCollaboratorsForUser(@user_id, @callback)
it "should return the default number", ->
@callback.calledWith(null, @Settings.defaultPlanCode.collaborators).should.equal true
describe "when the user has features", ->
beforeEach ->
@user.features =
collaborators: 21
@callback = sinon.stub()
@LimitationsManager.allowedNumberOfCollaboratorsForUser(@user_id, @callback)
it "should return the number of collaborators the user is allowed", ->
@callback.calledWith(null, @user.features.collaborators).should.equal true
describe "canAddXCollaborators", -> describe "canAddXCollaborators", ->
beforeEach -> beforeEach ->