2019-05-29 05:21:06 -04:00
|
|
|
/* eslint-disable
|
|
|
|
max-len,
|
|
|
|
no-return-assign,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* DS103: Rewrite code to no longer use __guard__
|
|
|
|
* DS207: Consider shorter variations of null checks
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
const SandboxedModule = require('sandboxed-module')
|
|
|
|
const sinon = require('sinon')
|
|
|
|
const modulePath = require('path').join(
|
|
|
|
__dirname,
|
|
|
|
'../../../../app/src/Features/Security/RateLimiterMiddleware'
|
|
|
|
)
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('RateLimiterMiddleware', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.AuthenticationController = {
|
|
|
|
getLoggedInUserId: () => {
|
|
|
|
return __guard__(
|
|
|
|
__guard__(
|
|
|
|
this.req != null ? this.req.session : undefined,
|
|
|
|
x1 => x1.user
|
|
|
|
),
|
|
|
|
x => x._id
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.RateLimiterMiddleware = SandboxedModule.require(modulePath, {
|
|
|
|
requires: {
|
2019-09-26 10:15:58 -04:00
|
|
|
'settings-sharelatex': (this.settings = {}),
|
2019-05-29 05:21:06 -04:00
|
|
|
'../../infrastructure/RateLimiter': (this.RateLimiter = {}),
|
2020-02-12 10:13:09 -05:00
|
|
|
'./LoginRateLimiter': {},
|
2019-05-29 05:21:06 -04:00
|
|
|
'../Authentication/AuthenticationController': this
|
|
|
|
.AuthenticationController
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.req = { params: {} }
|
|
|
|
this.res = {
|
|
|
|
status: sinon.stub(),
|
|
|
|
write: sinon.stub(),
|
|
|
|
end: sinon.stub()
|
|
|
|
}
|
|
|
|
return (this.next = sinon.stub())
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('rateLimit', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.rateLimiter = this.RateLimiterMiddleware.rateLimit({
|
|
|
|
endpointName: 'test-endpoint',
|
|
|
|
params: ['project_id', 'doc_id'],
|
|
|
|
timeInterval: 42,
|
|
|
|
maxRequests: 12
|
|
|
|
})
|
|
|
|
return (this.req.params = {
|
|
|
|
project_id: (this.project_id = 'project-id'),
|
|
|
|
doc_id: (this.doc_id = 'doc-id')
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('when there is no session', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
|
|
|
|
this.req.ip = this.ip = '1.2.3.4'
|
|
|
|
return this.rateLimiter(this.req, this.res, this.next)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should call the rate limiter backend with the ip address', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.RateLimiter.addCount
|
|
|
|
.calledWith({
|
|
|
|
endpointName: 'test-endpoint',
|
|
|
|
timeInterval: 42,
|
|
|
|
throttle: 12,
|
|
|
|
subjectName: `${this.project_id}:${this.doc_id}:${this.ip}`
|
|
|
|
})
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should pass on to next()', function () {})
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('when smoke test user', function () {
|
|
|
|
beforeEach(function () {
|
2019-09-26 10:15:58 -04:00
|
|
|
this.req.session = {
|
|
|
|
user: {
|
|
|
|
_id: (this.user_id = 'smoke-test-user-id')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.settings.smokeTest = { userId: this.user_id }
|
|
|
|
this.RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
|
|
|
|
return this.rateLimiter(this.req, this.res, this.next)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should not call the rate limiter backend with the user_id', function () {
|
2019-09-26 10:15:58 -04:00
|
|
|
this.RateLimiter.addCount
|
|
|
|
.calledWith({
|
|
|
|
endpointName: 'test-endpoint',
|
|
|
|
timeInterval: 42,
|
|
|
|
throttle: 12,
|
|
|
|
subjectName: `${this.project_id}:${this.doc_id}:${this.user_id}`
|
|
|
|
})
|
|
|
|
.should.equal(false)
|
|
|
|
this.RateLimiter.addCount.callCount.should.equal(0)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should pass on to next()', function () {
|
2019-09-26 10:15:58 -04:00
|
|
|
return this.next.called.should.equal(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('when under the rate limit with logged in user', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.session = {
|
|
|
|
user: {
|
|
|
|
_id: (this.user_id = 'user-id')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
|
|
|
|
return this.rateLimiter(this.req, this.res, this.next)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should call the rate limiter backend with the user_id', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.RateLimiter.addCount
|
|
|
|
.calledWith({
|
|
|
|
endpointName: 'test-endpoint',
|
|
|
|
timeInterval: 42,
|
|
|
|
throttle: 12,
|
|
|
|
subjectName: `${this.project_id}:${this.doc_id}:${this.user_id}`
|
|
|
|
})
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should pass on to next()', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.next.called.should.equal(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('when under the rate limit with anonymous user', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.ip = this.ip = '1.2.3.4'
|
|
|
|
this.RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true)
|
|
|
|
return this.rateLimiter(this.req, this.res, this.next)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should call the rate limiter backend with the ip address', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.RateLimiter.addCount
|
|
|
|
.calledWith({
|
|
|
|
endpointName: 'test-endpoint',
|
|
|
|
timeInterval: 42,
|
|
|
|
throttle: 12,
|
|
|
|
subjectName: `${this.project_id}:${this.doc_id}:${this.ip}`
|
|
|
|
})
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should pass on to next()', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.next.called.should.equal(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('when over the rate limit', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.session = {
|
|
|
|
user: {
|
|
|
|
_id: (this.user_id = 'user-id')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.RateLimiter.addCount = sinon.stub().callsArgWith(1, null, false)
|
|
|
|
return this.rateLimiter(this.req, this.res, this.next)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should return a 429', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.status.calledWith(429).should.equal(true)
|
|
|
|
return this.res.end.called.should.equal(true)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should not continue', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.next.called.should.equal(false)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should log a warning', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.logger.warn
|
|
|
|
.calledWith(
|
|
|
|
{
|
|
|
|
endpointName: 'test-endpoint',
|
|
|
|
timeInterval: 42,
|
|
|
|
throttle: 12,
|
|
|
|
subjectName: `${this.project_id}:${this.doc_id}:${this.user_id}`
|
|
|
|
},
|
|
|
|
'rate limit exceeded'
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
function __guard__(value, transform) {
|
|
|
|
return typeof value !== 'undefined' && value !== null
|
|
|
|
? transform(value)
|
|
|
|
: undefined
|
|
|
|
}
|