Skip to content

Commit 202054c

Browse files
committed
Merge branch 'fhemberger-patch-1'
2 parents e96457d + 5ce9073 commit 202054c

File tree

4 files changed

+49
-12
lines changed

4 files changed

+49
-12
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var store = new ExpressBrute.MemoryStore(); // stores state locally, don't use t
2020
var bruteforce = new ExpressBrute(store);
2121

2222
app.post('/auth',
23-
bruteforce.prevent, // error 403 if we hit this route too often
23+
bruteforce.prevent, // error 429 if we hit this route too often
2424
function (req, res, next) {
2525
res.send('Success!');
2626
}
@@ -147,6 +147,7 @@ Changelog
147147
* NEW: Documentation updated to list some known store implementations.
148148
* CHANGED: Default failure callback is now `FailTooManyRequests`. `FailForbidden` remains an option for backwards compatiblity.
149149
* CHANGED: ExpressBrute.MemcachedStore is no longer included by default, and is now available as a separate module (because there are multiple store options it doesn't really make sense to include one by default).
150+
* CHANGED: `FailMark` no longer sets returns 403 Forbidden, instead does 429 TooManyRequets.
150151

151152
### v0.4.2
152153
* BUG: In some cases when no callbacks were supplied memcached would drop the request. Ensure that memcached always sees a callback even if ExpressBrute isn't given one.

index.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ ExpressBrute.prototype.getMiddleware = function (options) {
7676
reset: reset
7777
};
7878
}
79-
79+
8080

8181
// filter request
8282
this.store.get(key, _.bind(function (err, value) {
@@ -154,11 +154,21 @@ ExpressBrute.prototype.getIPFromRequest = function (req) {
154154
return req.connection.remoteAddress;
155155
};
156156

157+
var setRetryAfter = function (res, nextValidRequestDate) {
158+
var secondUntilNextRequest = Math.ceil((nextValidRequestDate.getTime() - Date.now())/1000);
159+
res.header('Retry-After', secondUntilNextRequest);
160+
};
161+
ExpressBrute.FailTooManyRequests = function (req, res, next, nextValidRequestDate) {
162+
setRetryAfter(res, nextValidRequestDate);
163+
res.send(429, {error: {text: "Too many requests in this time frame.", nextValidRequestDate: nextValidRequestDate}});
164+
};
157165
ExpressBrute.FailForbidden = function (req, res, next, nextValidRequestDate) {
166+
setRetryAfter(res, nextValidRequestDate);
158167
res.send(403, {error: {text: "Too many requests in this time frame.", nextValidRequestDate: nextValidRequestDate}});
159168
};
160169
ExpressBrute.FailMark = function (req, res, next, nextValidRequestDate) {
161-
res.status(403);
170+
res.status(429);
171+
setRetryAfter(res, nextValidRequestDate);
162172
res.nextValidRequestDate = nextValidRequestDate;
163173
next();
164174
};
@@ -170,6 +180,6 @@ ExpressBrute.defaults = {
170180
refreshTimeoutOnRequest: true,
171181
minWait: 500,
172182
maxWait: 1000*60*15, // 15 minutes
173-
failCallback: ExpressBrute.FailForbidden
183+
failCallback: ExpressBrute.FailTooManyRequests
174184
};
175185
ExpressBrute.instanceCount = 0;

mock/ResponseMock.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = function () {
22
return {
33
status: jasmine.createSpy(),
4-
send: jasmine.createSpy()
4+
send: jasmine.createSpy(),
5+
header: jasmine.createSpy()
56
};
67
};

spec/ExpessBrute.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe("express brute", function () {
113113
expect(errorSpy).toHaveBeenCalled();
114114
expect(errorSpy.mostRecentCall.args[3].getTime()).toEqual(expectedTime);
115115
});
116-
116+
117117
});
118118
it('works even after the maxwait is reached', function () {
119119
brute = new ExpressBrute(store, {
@@ -406,7 +406,7 @@ describe("express brute", function () {
406406
runs(function () {
407407
expect(errorSpy).toHaveBeenCalled();
408408
expect(errorSpy2).not.toHaveBeenCalled();
409-
409+
410410
brute.prevent(req(), new ResponseMock(), nextSpy);
411411
brute2.prevent(req(), new ResponseMock(), nextSpy);
412412
});
@@ -474,10 +474,23 @@ describe("express brute", function () {
474474
store = new ExpressBrute.MemoryStore();
475475
req = function () { return { connection: { remoteAddress: '1.2.3.4' }}; };
476476
nextSpy = jasmine.createSpy();
477-
477+
478+
});
479+
it('can return a 429 Too Many Requests', function () {
480+
var res = new ResponseMock();
481+
brute = new ExpressBrute(store, {
482+
freeRetries: 0,
483+
minWait: 10,
484+
maxWait: 100,
485+
failCallback: ExpressBrute.FailTooManyRequests
486+
});
487+
brute.prevent(req(), res, nextSpy);
488+
brute.prevent(req(), res, nextSpy);
489+
expect(res.send).toHaveBeenCalled();
490+
expect(res.send.mostRecentCall.args[0]).toEqual(429);
478491
});
479-
it('can return a 403 forbidden', function () {
480-
var res = {send: jasmine.createSpy()};
492+
it('can return a 403 Forbidden', function () {
493+
var res = new ResponseMock();
481494
brute = new ExpressBrute(store, {
482495
freeRetries: 0,
483496
minWait: 10,
@@ -490,7 +503,7 @@ describe("express brute", function () {
490503
expect(res.send.mostRecentCall.args[0]).toEqual(403);
491504
});
492505
it('can mark a response as failed, but continue processing', function () {
493-
var res = {status: jasmine.createSpy()};
506+
var res = new ResponseMock();
494507
brute = new ExpressBrute(store, {
495508
freeRetries: 0,
496509
minWait: 10,
@@ -499,10 +512,22 @@ describe("express brute", function () {
499512
});
500513
brute.prevent(req(), res, nextSpy);
501514
brute.prevent(req(), res, nextSpy);
502-
expect(res.status).toHaveBeenCalledWith(403);
515+
expect(res.status).toHaveBeenCalledWith(429);
503516
expect(nextSpy.calls.length).toEqual(2);
504517
expect(res.nextValidRequestDate).toBeDefined();
505518
expect(res.nextValidRequestDate instanceof Date).toBeTruthy();
506519
});
520+
it('sets Retry-After', function () {
521+
var res = new ResponseMock();
522+
brute = new ExpressBrute(store, {
523+
freeRetries: 0,
524+
minWait: 10,
525+
maxWait: 100,
526+
failCallback: ExpressBrute.FailTooManyRequests
527+
});
528+
brute.prevent(req(), res, nextSpy);
529+
brute.prevent(req(), res, nextSpy);
530+
expect(res.header).toHaveBeenCalledWith('Retry-After', 1);
531+
});
507532
});
508533
});

0 commit comments

Comments
 (0)