Skip to content

Commit 45ffed7

Browse files
committed
Added “attachResetToRequest” option
1 parent 337837e commit 45ffed7

File tree

3 files changed

+64
-20
lines changed

3 files changed

+64
-20
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ Classes
3232
### ExpressBrute(store, options)
3333
- `store` An instance of `ExpressBrute.MemoryStore` or `ExpressBrute.MemcachedStore`
3434
- `options`
35-
- `freeRetries` The number of retires the user has before they need to start waiting (default: 2)
36-
- `minWait` The initial wait time (in milliseconds) after the user runs out of retries (default: 500 milliseconds)
37-
- `maxWait` The maximum amount of time (in milliseconds) between requests the user needs to wait (default: 15 minutes). The wait for a given request is determined by adding the time the user needed to wait for the previous two requests.
38-
- `lifetime` The length of time (in seconds since the last request) to remember the number of requests that have been made by an IP. By default it will be set to `maxWait * the number of attempts before you hit maxWait` to discourage simply waiting for the lifetime to expire before resuming an attack. With default values this is about 6 hours.
35+
- `freeRetries` The number of retires the user has before they need to start waiting (default: 2)
36+
- `minWait` The initial wait time (in milliseconds) after the user runs out of retries (default: 500 milliseconds)
37+
- `maxWait` The maximum amount of time (in milliseconds) between requests the user needs to wait (default: 15 minutes). The wait for a given request is determined by adding the time the user needed to wait for the previous two requests.
38+
- `lifetime` The length of time (in seconds since the last request) to remember the number of requests that have been made by an IP. By default it will be set to `maxWait * the number of attempts before you hit maxWait` to discourage simply waiting for the lifetime to expire before resuming an attack. With default values this is about 6 hours.
3939
- `failCallback` gets called with (`req`, `resp`, `next`, `nextValidRequestDate`) when a request is rejected (default: ExpressBrute.FailForbidden)
40-
- `proxyDepth` Specifies how many levels of the `X-Forwarded-For` header to trust. If your web server is behind a CDN and/or load balancer you'll need to set this to however many levels of proxying it's behind to get a valid IP. Setting this too high allows attackers to get around brute force protection by spoofing the `X-Forwarded-For` header, so don't set it higher than you need to (default: 0)
40+
- `proxyDepth` Specifies how many levels of the `X-Forwarded-For` header to trust. If your web server is behind a CDN and/or load balancer you'll need to set this to however many levels of proxying it's behind to get a valid IP. Setting this too high allows attackers to get around brute force protection by spoofing the `X-Forwarded-For` header, so don't set it higher than you need to (default: 0)
41+
- `attachResetToRequest` Specify whether or not a simplified reset method should be attached at `req.brute.reset`. The simplified method takes only a callback, and resets all `ExpressBrute` middleware that was called on the current request. If multiple instances of `ExpressBrute` have middleware on the same request, only those with `attachResetToRequest` set to true will be reset (default: true)
4142

4243
### ExpressBrute.MemoryStore()
4344
An in-memory store for persisting request counts. Don't use this in production.
@@ -108,6 +109,7 @@ var userBruteforce = new ExpressBrute(store, {
108109
var globalBruteforce = new ExpressBrute(store, {
109110
freeRetries: 1000,
110111
proxyDepth: 1,
112+
attachResetToRequest: false,
111113
winWait: 25*60*60*1000, // 1 day 1 hour (should never reach this wait time)
112114
maxWait: 25*60*60*1000, // 1 day 1 hour (should never reach this wait time)
113115
lifetime: 24*60*60*1000, // 1 day

index.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,26 @@ ExpressBrute.prototype.getMiddleware = function (key) {
5353
keyFunc(req, res, _.bind(function (key) {
5454
key = getKey([this.getIPFromRequest(req), this.name, key]);
5555

56-
// attach a simpler "reset" functio to req.brute.reset
57-
var reset = _.bind(function (callback) {
58-
this.store.reset(key, callback);
59-
}, this);
60-
if (req.brute && req.brute.reset) {
61-
// wrap existing reset if one exists
62-
var oldReset = req.brute.reset;
63-
var newReset = reset;
64-
reset = function (callback) {
65-
oldReset(function () {
66-
newReset(callback);
67-
});
56+
// attach a simpler "reset" function to req.brute.reset
57+
if (this.options.attachResetToRequest) {
58+
var reset = _.bind(function (callback) {
59+
this.store.reset(key, callback);
60+
}, this);
61+
if (req.brute && req.brute.reset) {
62+
// wrap existing reset if one exists
63+
var oldReset = req.brute.reset;
64+
var newReset = reset;
65+
reset = function (callback) {
66+
oldReset(function () {
67+
newReset(callback);
68+
});
69+
};
70+
}
71+
req.brute = {
72+
reset: reset
6873
};
6974
}
70-
req.brute = {
71-
reset: reset
72-
};
75+
7376

7477
// filter request
7578
this.store.get(key, _.bind(function (err, value) {
@@ -141,6 +144,7 @@ ExpressBrute.MemcachedStore = require('./lib/MemcachedStore');
141144
ExpressBrute.defaults = {
142145
freeRetries: 2,
143146
proxyDepth: 0,
147+
attachResetToRequest: true,
144148
minWait: 500,
145149
maxWait: 1000*60*15, // 15 minutes
146150
failCallback: ExpressBrute.FailForbidden

spec/ExpessBrute.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,14 @@ describe("express brute", function () {
236236
mid(req(), new ResponseMock(), nextSpy);
237237
expect(nextSpy.calls.length).toEqual(2);
238238
});
239+
it ('respects the attachResetToRequest', function () {
240+
brute.options.attachResetToRequest = false;
241+
var firstReq;
242+
243+
brute.prevent(firstReq = req(), new ResponseMock(), nextSpy);
244+
expect(nextSpy.calls.length).toEqual(1);
245+
expect(firstReq.brute).toBeUndefined();
246+
});
239247
});
240248
describe('proxy severs', function () {
241249
var brute, store, errorSpy, nextSpy, req, req2;
@@ -361,6 +369,36 @@ describe("express brute", function () {
361369
});
362370

363371
});
372+
it ('resets only one brute instance when the req.reset shortcut is called but attachResetToRequest is false on one', function () {
373+
brute2 = new ExpressBrute(store, {
374+
freeRetries: 1,
375+
minWait: 100,
376+
maxWait: 1000,
377+
failCallback: errorSpy2,
378+
lifetime: 0,
379+
attachResetToRequest: false
380+
});
381+
382+
var failReq = req();
383+
var successSpy = jasmine.createSpy("success spy");
384+
brute.prevent(req(), new ResponseMock(), nextSpy);
385+
brute2.prevent(req(), new ResponseMock(), nextSpy);
386+
brute2.prevent(req(), new ResponseMock(), nextSpy);
387+
expect(errorSpy).not.toHaveBeenCalled();
388+
expect(errorSpy2).not.toHaveBeenCalled();
389+
390+
brute.prevent(failReq, new ResponseMock(), nextSpy);
391+
brute2.prevent(failReq, new ResponseMock(), nextSpy);
392+
expect(errorSpy).toHaveBeenCalled();
393+
expect(errorSpy2).toHaveBeenCalled();
394+
395+
failReq.brute.reset(function () {
396+
brute.prevent(failReq, new ResponseMock(), successSpy);
397+
brute2.prevent(failReq, new ResponseMock(), successSpy);
398+
expect(successSpy.calls.length).toEqual(1);
399+
});
400+
401+
});
364402
});
365403
describe("failure handlers", function () {
366404
var brute, store, req, done, nextSpy;

0 commit comments

Comments
 (0)