First commit
This commit is contained in:
436
framework/node_modules/node-rate-limiter-flexible/test/ExpressBruteFlexible.test.js
generated
vendored
Normal file
436
framework/node_modules/node-rate-limiter-flexible/test/ExpressBruteFlexible.test.js
generated
vendored
Normal file
@@ -0,0 +1,436 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable prefer-promise-reject-errors */
|
||||
const { describe, it, beforeEach } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const redisMock = require('redis-mock');
|
||||
const Memcached = require('memcached-mock');
|
||||
const ExpressBruteFlexible = require('../lib/ExpressBruteFlexible');
|
||||
const limiters = require('../index');
|
||||
const { redisEvalMock } = require('./helper');
|
||||
|
||||
const makeRequest = (middleware, req, res, next) => new Promise((resolve) => {
|
||||
middleware(req, res, (err) => {
|
||||
if (err) {
|
||||
resolve(err);
|
||||
} else {
|
||||
next();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExpressBruteFlexible', function ExpressBruteFlexibleTest() {
|
||||
this.timeout(10000);
|
||||
|
||||
const resObj = {
|
||||
header: () => {
|
||||
},
|
||||
status: () => {
|
||||
},
|
||||
send: () => {
|
||||
},
|
||||
};
|
||||
|
||||
const memcacheMockClient = new Memcached('localhost:11211');
|
||||
const redisMockClient = redisMock.createClient();
|
||||
redisMockClient.eval = redisEvalMock(redisMockClient);
|
||||
|
||||
const mongoCollection = {
|
||||
createIndex: () => {
|
||||
},
|
||||
findOneAndUpdate: () => {
|
||||
},
|
||||
findOne: () => {
|
||||
},
|
||||
deleteOne: () => {
|
||||
},
|
||||
};
|
||||
|
||||
const mongoClientMock = {
|
||||
db: () => {
|
||||
},
|
||||
};
|
||||
|
||||
const mongoDb = {
|
||||
collection: () => {
|
||||
},
|
||||
};
|
||||
|
||||
sinon.stub(mongoDb, 'collection').callsFake(() => mongoCollection);
|
||||
sinon.stub(mongoClientMock, 'db').callsFake(() => mongoDb);
|
||||
|
||||
const mysqlClientMock = {
|
||||
query: () => {
|
||||
},
|
||||
};
|
||||
|
||||
const pgClientMock = {
|
||||
query: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
const pgClientErrored = {
|
||||
query: () => Promise.reject({ code: 0 }),
|
||||
};
|
||||
|
||||
beforeEach((done) => {
|
||||
memcacheMockClient.flush(() => {
|
||||
redisMockClient.flushall(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows 1 request with 1 free try', (done) => {
|
||||
const brute = new ExpressBruteFlexible('memory', {
|
||||
freeRetries: 1,
|
||||
});
|
||||
|
||||
brute.prevent({ ip: '127.0.0.1' }, resObj, () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows 2 requests with 2 free try', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.MEMCACHE, {
|
||||
storeClient: memcacheMockClient,
|
||||
freeRetries: 2,
|
||||
handleStoreError(err) {
|
||||
done(err);
|
||||
},
|
||||
});
|
||||
|
||||
const next = sinon.spy();
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, resObj, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, resObj, next),
|
||||
]).then(() => {
|
||||
expect(next.calledTwice).to.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('works 0 free try', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.MEMCACHE, {
|
||||
storeClient: memcacheMockClient,
|
||||
freeRetries: 0,
|
||||
handleStoreError(err) {
|
||||
done(err);
|
||||
},
|
||||
failCallback(req, res, next) {
|
||||
next({ message: 'blocked' });
|
||||
},
|
||||
});
|
||||
|
||||
const next = sinon.spy();
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, resObj, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, resObj, next),
|
||||
]).then(() => {
|
||||
expect(next.calledOnce).to.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('blocks the second request when no free tries and calls failCallback', () => {
|
||||
const brute = new ExpressBruteFlexible('memory', {
|
||||
freeRetries: 0,
|
||||
minWait: 1000,
|
||||
failCallback(req, res, next, nextValidRequestDate) {
|
||||
res.status(403);
|
||||
res.send({
|
||||
error: {
|
||||
nextValidRequestDate,
|
||||
},
|
||||
});
|
||||
next();
|
||||
},
|
||||
});
|
||||
|
||||
const next = sinon.spy();
|
||||
const mockRes = Object.assign({}, resObj);
|
||||
const resStatusSpy = sinon.spy(mockRes, 'status');
|
||||
const resSendSpy = sinon.spy(mockRes, 'send');
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next)
|
||||
.then(() => {
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next)
|
||||
.then(() => {
|
||||
expect(resStatusSpy.calledWith(403)).to.equal(true);
|
||||
const spySendCall = resSendSpy.getCall(0);
|
||||
const blockDuration = spySendCall.args[0].error.nextValidRequestDate.getTime() - Date.now();
|
||||
expect(blockDuration > 0 && blockDuration <= 1000).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('maxWait limits maximum block duration on high traffic', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.REDIS, {
|
||||
storeClient: redisMockClient,
|
||||
freeRetries: 0,
|
||||
minWait: 2000,
|
||||
maxWait: 3000,
|
||||
failCallback(req, res, next, nextValidRequestDate) {
|
||||
res.send({
|
||||
error: {
|
||||
nextValidRequestDate,
|
||||
},
|
||||
});
|
||||
next();
|
||||
},
|
||||
});
|
||||
|
||||
let maximumBlockDuration = 0;
|
||||
const mockRes = Object.assign({}, resObj);
|
||||
mockRes.send = (obj) => {
|
||||
const blockDuration = obj.error.nextValidRequestDate.getTime() - Date.now();
|
||||
if (blockDuration > maximumBlockDuration) {
|
||||
maximumBlockDuration = blockDuration;
|
||||
}
|
||||
};
|
||||
|
||||
const next = sinon.spy();
|
||||
const resSendSpy = sinon.spy(mockRes, 'send');
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
]).then(() => {
|
||||
setTimeout(() => {
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
]).then(() => {
|
||||
setTimeout(() => {
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
]).then(() => {
|
||||
setTimeout(() => {
|
||||
expect(maximumBlockDuration <= 3000).to.be.true;
|
||||
expect(resSendSpy.callCount).to.equal(3);
|
||||
done();
|
||||
}, 4100);
|
||||
});
|
||||
}, 3100);
|
||||
});
|
||||
}, 2100);
|
||||
});
|
||||
});
|
||||
|
||||
it('block time grows fibonacci-like way', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.REDIS, {
|
||||
storeClient: redisMockClient,
|
||||
freeRetries: 0,
|
||||
minWait: 2000,
|
||||
maxWait: 10000,
|
||||
lifetime: 10000,
|
||||
failCallback(req, res, next, nextValidRequestDate) {
|
||||
res.send({
|
||||
error: {
|
||||
nextValidRequestDate,
|
||||
},
|
||||
});
|
||||
next();
|
||||
},
|
||||
});
|
||||
|
||||
let sequenceLength = 0;
|
||||
const mockRes = Object.assign({}, resObj);
|
||||
mockRes.send = (obj) => {
|
||||
const blockDuration = obj.error.nextValidRequestDate.getTime() - Date.now();
|
||||
if (blockDuration > 1000 && blockDuration <= 2000 && sequenceLength === 0) {
|
||||
sequenceLength++;
|
||||
}
|
||||
if (blockDuration > 1000 && blockDuration <= 2000 && sequenceLength === 1) {
|
||||
sequenceLength++;
|
||||
}
|
||||
if (blockDuration > 2000 && blockDuration <= 4000 && sequenceLength === 2) {
|
||||
sequenceLength++;
|
||||
}
|
||||
};
|
||||
|
||||
const next = sinon.spy();
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
]).then(() => {
|
||||
setTimeout(() => {
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
]).then(() => {
|
||||
setTimeout(() => {
|
||||
Promise.all([
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
makeRequest(brute.prevent, { ip: '127.0.0.1' }, mockRes, next),
|
||||
]).then(() => {
|
||||
setTimeout(() => {
|
||||
expect(sequenceLength).to.equal(3);
|
||||
done();
|
||||
}, 4100);
|
||||
});
|
||||
}, 2100);
|
||||
});
|
||||
}, 2100);
|
||||
});
|
||||
});
|
||||
|
||||
it('attaches reset to request by default and reset works', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.REDIS, {
|
||||
storeClient: redisMockClient,
|
||||
freeRetries: 1,
|
||||
minWait: 1000,
|
||||
maxWait: 5000,
|
||||
});
|
||||
|
||||
const req = { ip: '127.0.0.1' };
|
||||
|
||||
brute.prevent(req, resObj, () => {
|
||||
expect(typeof req.brute.reset).to.equal('function');
|
||||
req.brute.reset(() => {
|
||||
brute.prevent(req, resObj, () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not attach request if option is false', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.REDIS, {
|
||||
storeClient: redisMockClient,
|
||||
freeRetries: 1,
|
||||
minWait: 1000,
|
||||
maxWait: 5000,
|
||||
attachResetToRequest: false,
|
||||
});
|
||||
|
||||
const req = { ip: '127.0.0.1' };
|
||||
|
||||
brute.prevent(req, resObj, () => {
|
||||
expect(typeof req.brute === 'undefined' || typeof req.brute.reset === 'undefined').to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('getMiddleware returns middleware function and works', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.REDIS, {
|
||||
storeClient: redisMockClient,
|
||||
freeRetries: 1,
|
||||
minWait: 1000,
|
||||
maxWait: 5000,
|
||||
attachResetToRequest: false,
|
||||
});
|
||||
|
||||
const middleware = brute.getMiddleware();
|
||||
|
||||
const req = { ip: '127.0.0.1' };
|
||||
|
||||
middleware(req, resObj, done);
|
||||
});
|
||||
|
||||
it('ignores IP from key if getMiddleware is with option ignoreIP=false', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.REDIS, {
|
||||
storeClient: redisMockClient,
|
||||
freeRetries: 1,
|
||||
minWait: 1000,
|
||||
maxWait: 5000,
|
||||
attachResetToRequest: false,
|
||||
handleStoreError(err) {
|
||||
done(err);
|
||||
},
|
||||
});
|
||||
|
||||
const getKeySpy = sinon.spy(ExpressBruteFlexible, '_getKey');
|
||||
const middleware = brute.getMiddleware({
|
||||
ignoreIP: true,
|
||||
});
|
||||
|
||||
const req = { ip: '127.0.0.1' };
|
||||
|
||||
middleware(req, resObj, () => {
|
||||
const getKeySpyCall = getKeySpy.getCall(0);
|
||||
expect(getKeySpyCall.lastArg[0]).to.not.equal(req.ip);
|
||||
getKeySpy.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('memory limiters created internally by storeType', () => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.MEMORY);
|
||||
expect(brute.counterLimiter instanceof limiters.RateLimiterMemory).to.be.true;
|
||||
expect(brute.blockLimiter instanceof limiters.RateLimiterMemory).to.be.true;
|
||||
});
|
||||
|
||||
it('memcache limiters created internally by storeType', () => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.MEMCACHE, {
|
||||
storeClient: memcacheMockClient,
|
||||
});
|
||||
expect(brute.counterLimiter instanceof limiters.RateLimiterMemcache).to.be.true;
|
||||
expect(brute.blockLimiter instanceof limiters.RateLimiterMemcache).to.be.true;
|
||||
});
|
||||
|
||||
it('mongo limiters created internally by storeType', () => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.MONGO, {
|
||||
storeClient: mongoClientMock,
|
||||
});
|
||||
expect(brute.counterLimiter instanceof limiters.RateLimiterMongo).to.be.true;
|
||||
expect(brute.blockLimiter instanceof limiters.RateLimiterMongo).to.be.true;
|
||||
});
|
||||
|
||||
it('redis limiters created internally by storeType', () => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.REDIS, {
|
||||
storeClient: redisMockClient,
|
||||
});
|
||||
expect(brute.counterLimiter instanceof limiters.RateLimiterRedis).to.be.true;
|
||||
expect(brute.blockLimiter instanceof limiters.RateLimiterRedis).to.be.true;
|
||||
});
|
||||
|
||||
it('mysql limiters created internally by storeType', () => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.MYSQL, {
|
||||
storeClient: mysqlClientMock,
|
||||
storeType: 'client',
|
||||
});
|
||||
expect(brute.counterLimiter instanceof limiters.RateLimiterMySQL).to.be.true;
|
||||
expect(brute.blockLimiter instanceof limiters.RateLimiterMySQL).to.be.true;
|
||||
});
|
||||
|
||||
it('postgres limiters created internally by storeType', () => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.POSTGRES, {
|
||||
storeClient: pgClientMock,
|
||||
storeType: 'client',
|
||||
});
|
||||
expect(brute.counterLimiter instanceof limiters.RateLimiterPostgres).to.be.true;
|
||||
expect(brute.blockLimiter instanceof limiters.RateLimiterPostgres).to.be.true;
|
||||
});
|
||||
|
||||
it('global reset works', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.MEMORY, {
|
||||
freeRetries: 1,
|
||||
});
|
||||
|
||||
const ip = '127.0.0.1';
|
||||
|
||||
brute.prevent({ ip }, resObj, () => {
|
||||
brute.reset(ip, undefined, () => {
|
||||
const key = ExpressBruteFlexible._getKey([ip, brute.name]);
|
||||
brute.freeLimiter.get(key)
|
||||
.then((res) => {
|
||||
expect(res).to.equal(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('global reset launches handleStoreError function', (done) => {
|
||||
const brute = new ExpressBruteFlexible(ExpressBruteFlexible.LIMITER_TYPES.POSTGRES, {
|
||||
storeClient: pgClientMock,
|
||||
storeType: 'client',
|
||||
freeRetries: 1,
|
||||
handleStoreError() {
|
||||
done();
|
||||
},
|
||||
});
|
||||
|
||||
const ip = '127.0.0.1';
|
||||
brute.freeLimiter.client = pgClientErrored;
|
||||
brute.reset(ip);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user