rittenhop-ghost/versions/5.94.2/node_modules/express-brute/spec/ExpessBrute.js

636 lines
20 KiB
JavaScript
Raw Normal View History

var chai = require('chai'),
should = chai.should(),
sinon = require('sinon'),
sinonChai = require('sinon-chai'),
ExpressBrute = require("../index"),
ResponseMock = require('../mock/ResponseMock');
chai.use(sinonChai);
describe("express brute", function () {
var clock;
before(function () {
clock = sinon.useFakeTimers();
});
after(function () {
clock.restore();
});
describe("basic functionality", function () {
it("has some memory stores", function () {
ExpressBrute.MemoryStore.should.exist;
});
it("can be initialized", function () {
var store = new ExpressBrute.MemoryStore();
var brute = new ExpressBrute(store);
brute.should.be.an.instanceof(ExpressBrute);
});
});
describe("behavior", function () {
var brute, store, errorSpy, nextSpy, req, req2;
beforeEach(function () {
store = new ExpressBrute.MemoryStore();
errorSpy = sinon.stub();
nextSpy = sinon.stub();
req = function () { return { ip: '1.2.3.4' }; };
req2 = function () { return { ip: '5.6.7.8' }; };
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: errorSpy
});
});
it('correctly calculates delays', function () {
brute.delays.should.deep.equal([10,10,20,30,50,80,100]);
});
it('respects free retries', function () {
brute = new ExpressBrute(store, {
freeRetries: 1,
minWait: 10,
maxWait: 100,
failCallback: errorSpy
});
brute.prevent(req(), new ResponseMock(), nextSpy);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
});
it('respects free retries even with clock skew', function() {
brute = new ExpressBrute(store, {
freeRetries: 1,
minWait: 10,
maxWait: 100,
failCallback: errorSpy
});
brute.prevent(req(), new ResponseMock(), nextSpy);
clock.tick(-100);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
});
it('correctly calculates delays when min and max wait are the same', function () {
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 10,
failCallback: errorSpy
});
brute.delays.should.deep.equal([10]);
});
it ('calls next when the request is allowed', function () {
brute.prevent(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
brute.prevent(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
});
it ('calls the error callback when requests come in too quickly', function () {
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
});
it ('allows requests as long as you wait long enough', function () {
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
clock.tick(brute.delays[0]+1);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
});
it ('allows requests if you reset the timer', function (done) {
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
var async = false;
brute.reset('1.2.3.4', null, function () {
async.should.be.true;
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
done();
});
async = true;
});
it('adds a reset shortcut to the request object', function (done) {
var reqObj = req();
brute.prevent(reqObj, new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
should.exist(reqObj.brute);
should.exist(reqObj.brute.reset);
reqObj.brute.reset(function () {
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
done();
});
});
it("resets even if you don't pass a callback", function (done) {
brute.prevent(req(), new ResponseMock(), nextSpy);
brute.reset('1.2.3.4', null);
process.nextTick(function () {
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
done();
});
});
it ('allows requests if you use different ips', function () {
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
nextSpy.should.have.been.calledOnce;
brute.prevent(req2(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
nextSpy.should.have.been.calledTwice;
});
it ('passes the correct next request time', function () {
var curTime = Date.now(),
expectedTime = curTime+brute.delays[0];
var oldNow = brute.now;
brute.now = function () { return curTime; };
brute.prevent(req(), new ResponseMock(), nextSpy);
brute.now = oldNow;
clock.tick(); // ensure some time has passed before calling the next time, caught a bug
brute.prevent(req(), new ResponseMock(), errorSpy);
errorSpy.should.have.been.called;
errorSpy.lastCall.args[3].getTime().should.equal(expectedTime);
});
it('works even after the maxwait is reached', function () {
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 10,
failCallback: function () {}
});
brute.prevent(req(), new ResponseMock(), nextSpy);
brute.prevent(req(), new ResponseMock(), nextSpy);
brute.options.failCallback = errorSpy;
clock.tick(brute.delays[0]+1);
var curTime = Date.now(),
expectedTime = curTime+brute.delays[0],
oldNow = brute.now;
brute.now = function () { return curTime; };
brute.prevent(req(), new ResponseMock(), nextSpy);
brute.now = oldNow;
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
errorSpy.lastCall.args[3].getTime().should.equal(expectedTime);
});
it('correctly calculates default lifetime', function () {
brute = new ExpressBrute(store, {
freeRetries: 1,
minWait: 100,
maxWait: 1000,
failCallback: errorSpy
});
brute.options.lifetime.should.equal(8);
});
it('allows requests after the lifetime causes them to expire', function () {
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10000,
maxWait: 10000,
lifetime: 1,
failCallback: errorSpy
});
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
clock.tick((brute.options.lifetime*1000)+1);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.calledOnce;
});
it("doesn't extend the lifetime if refreshTimeoutOnRequest is false", function () {
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10000,
maxWait: 10000,
lifetime: 1,
refreshTimeoutOnRequest: false,
failCallback: errorSpy
});
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.calledOnce;
clock.tick((brute.options.lifetime*500));
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.calledTwice;
clock.tick((brute.options.lifetime*500)+1);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.calledTwice;
});
it('does extend the lifetime if refreshTimeoutOnRequest is true', function () {
brute = new ExpressBrute(store, {
freeRetries: 1,
minWait: 10000,
maxWait: 10000,
lifetime: 1,
failCallback: errorSpy
});
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
clock.tick((brute.options.lifetime*500));
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
clock.tick((brute.options.lifetime*500)+1);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.calledOnce;
});
it('allows failCallback to be overridden', function () {
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10000,
maxWait: 10000,
lifetime: 1,
failCallback: errorSpy
});
var errorSpy2 = sinon.stub();
var mid = brute.getMiddleware({
failCallback: errorSpy2
});
mid(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
errorSpy2.should.not.have.been.called;
mid(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
errorSpy2.should.have.been.called;
});
});
describe("multiple keys", function () {
var brute, store, errorSpy, nextSpy, req;
beforeEach(function () {
store = new ExpressBrute.MemoryStore();
errorSpy = sinon.stub();
nextSpy = sinon.stub();
req = function () { return { ip: '1.2.3.4' }; };
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: errorSpy
});
});
it ('tracks keys separately', function () {
var first = brute.getMiddleware({key: 'first' });
var second = brute.getMiddleware({key: 'second' });
first(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
second(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledTwice;
first(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledTwice;
second(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledTwice;
});
it ('supports key functions', function () {
req = function () {
return {
ip: '1.2.3.4',
someData: "something cool"
};
};
var first = brute.getMiddleware({key: function(req, res, next) { next(req.someData); } });
var second = brute.getMiddleware({key: "something cool" });
first(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
first(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
second(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
});
it('supports ignoring IP', function() {
var req = function () {
return {
ip: '1.2.3.4'
};
};
var req2 = function () {
return {
ip: '4.3.2.1'
};
};
var first = brute.getMiddleware({key: "something cool", ignoreIP: true});
first(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
first(req2(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
});
it ('supports brute.reset', function () {
var mid = brute.getMiddleware({key: 'withAKey' });
mid(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
brute.reset("1.2.3.4", "withAKey");
mid(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledTwice;
});
it ('supports req.reset shortcut', function () {
var firstReq, mid = brute.getMiddleware({key: 'withAKey' });
mid(firstReq = req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
firstReq.brute.reset();
mid(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledTwice;
});
it ('respects the attachResetToRequest', function () {
brute.options.attachResetToRequest = false;
var firstReq;
brute.prevent(firstReq = req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledOnce;
should.not.exist(firstReq.brute);
});
});
describe("multiple brute instances", function () {
var brute, brute2, store, errorSpy, errorSpy2, nextSpy, req;
beforeEach(function () {
store = new ExpressBrute.MemoryStore();
errorSpy = sinon.stub();
errorSpy2 = sinon.stub();
nextSpy = sinon.stub();
req = function () { return { ip: '1.2.3.4' }; };
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 100,
maxWait: 1000,
failCallback: errorSpy,
lifetime: 0
});
brute2 = new ExpressBrute(store, {
freeRetries: 1,
minWait: 100,
maxWait: 1000,
failCallback: errorSpy2,
lifetime: 0
});
});
it ('tracks hits separately for each instance', function () {
brute.prevent(req(), new ResponseMock(), nextSpy);
brute2.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
errorSpy2.should.not.have.been.called;
brute.prevent(req(), new ResponseMock(), nextSpy);
brute2.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
errorSpy2.should.not.have.been.called;
brute.prevent(req(), new ResponseMock(), nextSpy);
brute2.prevent(req(), new ResponseMock(), nextSpy);
nextSpy.should.have.been.calledThrice;
errorSpy2.should.have.been.called;
});
it ('resets both brute instances when the req.reset shortcut is called', function (done) {
var failReq = req();
var successSpy = sinon.stub();
brute.prevent(req(), new ResponseMock(), nextSpy);
brute2.prevent(req(), new ResponseMock(), nextSpy);
brute2.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
errorSpy2.should.not.have.been.called;
brute.prevent(failReq, new ResponseMock(), nextSpy);
brute2.prevent(failReq, new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
errorSpy2.should.have.been.called;
failReq.brute.reset(function () {
brute.prevent(failReq, new ResponseMock(), successSpy);
brute2.prevent(failReq, new ResponseMock(), successSpy);
successSpy.should.have.been.calledTwice;
done();
});
});
it ('resets only one brute instance when the req.reset shortcut is called but attachResetToRequest is false on one', function (done) {
brute2 = new ExpressBrute(store, {
freeRetries: 1,
minWait: 100,
maxWait: 1000,
failCallback: errorSpy2,
lifetime: 0,
attachResetToRequest: false
});
var failReq = req();
var successStub = sinon.stub();
brute.prevent(req(), new ResponseMock(), nextSpy);
brute2.prevent(req(), new ResponseMock(), nextSpy);
brute2.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
errorSpy2.should.not.have.been.called;
brute.prevent(failReq, new ResponseMock(), nextSpy);
brute2.prevent(failReq, new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
errorSpy2.should.have.been.called;
failReq.brute.reset(function () {
brute.prevent(failReq, new ResponseMock(), successStub);
brute2.prevent(failReq, new ResponseMock(), successStub);
successStub.should.have.been.called.once;
done();
});
});
});
describe("failure handlers", function () {
var brute, store, req, nextSpy;
beforeEach(function () {
store = new ExpressBrute.MemoryStore();
req = function () { return { ip: '1.2.3.4' }; };
nextSpy = sinon.stub();
});
it('can return a 429 Too Many Requests', function () {
var res = new ResponseMock();
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: ExpressBrute.FailTooManyRequests
});
brute.prevent(req(), res, nextSpy);
brute.prevent(req(), res, nextSpy);
res.send.should.have.been.called;
res.status.lastCall.args[0].should.equal(429);
});
it('can return a 403 Forbidden', function () {
var res = new ResponseMock();
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: ExpressBrute.FailForbidden
});
brute.prevent(req(), res, nextSpy);
brute.prevent(req(), res, nextSpy);
res.send.should.have.been.called;
res.status.lastCall.args[0].should.equal(403);
});
it('can mark a response as failed, but continue processing', function () {
var res = new ResponseMock();
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: ExpressBrute.FailMark
});
brute.prevent(req(), res, nextSpy);
brute.prevent(req(), res, nextSpy);
res.status.should.have.been.calledWith(429);
nextSpy.should.have.been.calledTwice;
res.nextValidRequestDate.should.exist;
res.nextValidRequestDate.should.be.instanceof(Date);
});
it('sets Retry-After', function () {
var res = new ResponseMock();
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: ExpressBrute.FailTooManyRequests
});
brute.prevent(req(), res, nextSpy);
brute.prevent(req(), res, nextSpy);
res.header.should.have.been.calledWith('Retry-After', 1);
});
});
describe("store error handling", function () {
var brute, store, errorSpy, storeErrorSpy, nextSpy, req, res, err;
beforeEach(function () {
store = new ExpressBrute.MemoryStore();
errorSpy = sinon.stub();
storeErrorSpy = sinon.stub();
nextSpy = sinon.stub();
req = { ip: '1.2.3.4' };
res = new ResponseMock();
err = "Example Error";
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: errorSpy,
handleStoreError: storeErrorSpy
});
});
it('should handle get errors', function () {
sinon.stub(store, 'get', function (key, callback) {
callback(err);
});
brute.prevent(req, res, nextSpy);
storeErrorSpy.should.have.been.calledWithMatch({
req: req,
res: res,
next: nextSpy,
message: 'Cannot get request count',
parent: err
});
errorSpy.should.not.have.been.called;
nextSpy.should.not.have.been.called;
});
it('should handle set errors', function () {
sinon.stub(store, 'set', function (key, value, lifetime, callback) {
callback(err);
});
brute.prevent(req, res, nextSpy);
storeErrorSpy.should.have.been.calledWithMatch({
req: req,
res: res,
next: nextSpy,
message: 'Cannot increment request count',
parent: err
});
errorSpy.should.not.have.been.called;
nextSpy.should.not.have.been.called;
});
it('should handle reset errors', function () {
sinon.stub(store, 'reset', function (key, callback) {
callback(err);
});
var key = 'testKey';
brute.reset('1.2.3.4', key, nextSpy);
storeErrorSpy.should.have.been.calledWithMatch({
message: "Cannot reset request count",
parent: err,
key: ExpressBrute._getKey(['1.2.3.4', brute.name, key]),
ip: '1.2.3.4'
});
errorSpy.should.not.have.been.called;
nextSpy.should.not.have.been.called;
});
it('should throw an exception by default', function () {
brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: 10,
maxWait: 100,
failCallback: errorSpy
});
sinon.stub(store, 'get', function (key, callback) {
callback(err);
});
(function () {
brute.prevent(req, res, nextSpy);
}).should.throw({
message: 'Cannot get request count',
parent: err
});
errorSpy.should.not.have.been.called;
nextSpy.should.not.have.been.called;
});
});
describe('MemoryStore', function () {
it('supports timeouts of greater than 24.8 days (64 bit timeouts)', function () {
var yearInSeconds = 60*60*24*365;
var store = new ExpressBrute.MemoryStore();
var errorSpy = sinon.stub();
var nextSpy = sinon.stub();
var req = function () { return { ip: '1.2.3.4' }; };
var brute = new ExpressBrute(store, {
freeRetries: 0,
minWait: (yearInSeconds+100)*1000,
maxWait: (yearInSeconds+100)*1000,
lifetime: yearInSeconds,
failCallback: errorSpy
});
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.not.have.been.called;
clock.tick((brute.options.lifetime-100)*1000);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.called;
clock.tick(101*1000);
brute.prevent(req(), new ResponseMock(), nextSpy);
errorSpy.should.have.been.calledOnce;
});
});
});