Skip to content

Commit

Permalink
Merge 242befb into bc48c35
Browse files Browse the repository at this point in the history
  • Loading branch information
Favio Náquira committed Mar 24, 2020
2 parents bc48c35 + 242befb commit 010248e
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 0 deletions.
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -161,6 +161,15 @@ app.post('/auth',
);
```

Behind a Proxy
--------------
If your application is behind a proxy (Apache, Nginx) you should not forget set the **trust proxy** param on your Express application.
```
app.set('trust proxy', 1);
```
Please note the recommended value **1**. Don't use **true** becauseit will allow bypass the middleware of the express brute functionally, you just have to give the value **1** because of proxy depth.
You can check the whole explanation on the official link: [Express behind proxies](https://expressjs.com/en/guide/behind-proxies.html)

Changelog
---------
### v1.0.1
Expand Down
228 changes: 228 additions & 0 deletions README.md~
@@ -0,0 +1,228 @@
express-brute
=============
[![NPM Version](https://badge.fury.io/js/express-brute.png)](http://badge.fury.io/js/express-brute)
[![NPM Downloads](https://img.shields.io/npm/dm/express-brute.svg?maxAge=2592000)](http://badge.fury.io/js/express-brute)
[![Build Status](https://img.shields.io/travis/AdamPflug/express-brute.svg?maxAge=2592000)](https://travis-ci.org/AdamPflug/express-brute)
[![Coverage Status](https://img.shields.io/coveralls/AdamPflug/express-brute.svg?maxAge=2592000)](http://coveralls.io/github/AdamPflug/express-brute?branch=master)
[![Dependency Status](https://img.shields.io/david/AdamPflug/express-brute.svg?maxAge=2592000)](https://david-dm.org/adampflug/express-brute)

A brute-force protection middleware for express routes that rate-limits incoming requests, increasing the delay with each request in a fibonacci-like sequence.

Installation
------------
via npm:

$ npm install express-brute

A Simple Example
----------------
``` js
var ExpressBrute = require('express-brute');

var store = new ExpressBrute.MemoryStore(); // stores state locally, don't use this in production
var bruteforce = new ExpressBrute(store);

app.post('/auth',
bruteforce.prevent, // error 429 if we hit this route too often
function (req, res, next) {
res.send('Success!');
}
);
```

Classes
-------
### ExpressBrute(store, options)
- `store` An instance of `ExpressBrute.MemoryStore` or some other ExpressBrute store (see a list of known stores below).
- `options`
- `freeRetries` The number of retries the user has before they need to start waiting (default: 2)
- `minWait` The initial wait time (in milliseconds) after the user runs out of retries (default: 500 milliseconds)
- `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.
- `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.
- `failCallback` Gets called with (`req`, `resp`, `next`, `nextValidRequestDate`) when a request is rejected (default: ExpressBrute.FailForbidden)
- `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)
- `refreshTimeoutOnRequest` Defines whether the `lifetime` counts from the time of the last request that ExpressBrute didn't prevent for a given IP (true) or from of that IP's first request (false). Useful for allowing limits over fixed periods of time, for example: a limited number of requests per day. (Default: true). [More info](https://github.com/AdamPflug/express-brute/issues/14)
- `handleStoreError` Gets called whenever an error occurs with the persistent store from which ExpressBrute cannot recover. It is passed an object containing the properties `message` (a description of the message), `parent` (the error raised by the session store), and [`key`, `ip`] or [`req`, `res`, `next`] depending on whether or the error occurs during `reset` or in the middleware itself.

### ExpressBrute.MemoryStore()
An in-memory store for persisting request counts. Don't use this in production, instead choose one of the more robust store implementations listed below.


`ExpressBrute` Instance Methods
-------------------------------
- `prevent(req, res, next)` Middleware that will bounce requests that happen faster than
the current wait time by calling `failCallback`. Equivilent to `getMiddleware(null)`
- `getMiddleware(options)` Generates middleware that will bounce requests with the same `key` and IP address
that happen faster than the current wait time by calling `failCallback`.
Also attaches a function at `req.brute.reset` that can be called to reset the
counter for the current ip and key. This functions as the `reset` instance method,
but without the need to explicitly pass the `ip` and `key` paramters
- `key` can be a string or alternatively it can be a `function(req, res, next)`
that calls `next`, passing a string as the first parameter.
- `failCallback` Allows you to override the value of `failCallback` for this middleware
- `ignoreIP` Disregard IP address when matching requests if set to `true`. Defaults to `false`.
- `reset(ip, key, next)` Resets the wait time between requests back to its initial value. You can pass `null`
for `key` if you want to reset a request protected by `prevent`.

Built-in Failure Callbacks
---------------------------
There are some built-in callbacks that come with BruteExpress that handle some common use cases.
- `ExpressBrute.FailTooManyRequests` Terminates the request and responses with a 429 (Too Many Requests) error that has a `Retry-After` header and a JSON error message.
- `ExpressBrute.FailForbidden` Terminates the request and responds with a 403 (Forbidden) error that has a `Retry-After` header and a JSON error message. This is provided for compatibility with ExpressBrute versions prior to v0.5.0, for new users `FailTooManyRequests` is the preferred behavior.
- `ExpressBrute.FailMark` Sets res.nextValidRequestDate, the Retry-After header and the res.status=429, then calls next() to pass the request on to the appropriate routes.

`ExpressBrute` stores
---------------------
There are a number adapters that have been written to allow ExpressBrute to be used with different persistent storage implementations, some of the ones I know about include:
- [Memcached](https://github.com/AdamPflug/express-brute-memcached)
- [Redis](https://github.com/AdamPflug/express-brute-redis)
- [MongoDB](https://github.com/auth0/express-brute-mongo)
- [Mongoose](https://github.com/cbargren/express-brute-mongoose)
- [Sequelize (SQL)](https://github.com/maddy2get/express-brute-sequelize)
- [Knex.js (SQL)](https://github.com/llambda/brute-knex)
- [RethinkDB](https://github.com/llambda/brute-rethinkdb)
- [Loki.js](https://github.com/Requarks/express-brute-loki)
- [nedb](https://github.com/natsukagami/express-brute-nedb)
- [PostgreSQL](https://github.com/dmfay/express-brute-pg)
- [Couchbase](https://github.com/kvaillant/express-brute-couchbase)

If you write your own store and want me to add it to the list, just drop me an [email](mailto:adam.pflug@gmail.com) or [create an issue](https://github.com/AdamPflug/express-brute/issues/new).

A More Complex Example
----------------------
``` js
require('connect-flash');
var ExpressBrute = require('express-brute'),
MemcachedStore = require('express-brute-memcached'),
moment = require('moment'),
store;

if (config.environment == 'development'){
store = new ExpressBrute.MemoryStore(); // stores state locally, don't use this in production
} else {
// stores state with memcached
store = new MemcachedStore(['127.0.0.1'], {
prefix: 'NoConflicts'
});
}

var failCallback = function (req, res, next, nextValidRequestDate) {
req.flash('error', "You've made too many failed attempts in a short period of time, please try again "+moment(nextValidRequestDate).fromNow());
res.redirect('/login'); // brute force protection triggered, send them back to the login page
};
var handleStoreError = function (error) {
log.error(error); // log this error so we can figure out what went wrong
// cause node to exit, hopefully restarting the process fixes the problem
throw {
message: error.message,
parent: error.parent
};
}
// Start slowing requests after 5 failed attempts to do something for the same user
var userBruteforce = new ExpressBrute(store, {
freeRetries: 5,
minWait: 5*60*1000, // 5 minutes
maxWait: 60*60*1000, // 1 hour,
failCallback: failCallback,
handleStoreError: handleStoreError
});
// No more than 1000 login attempts per day per IP
var globalBruteforce = new ExpressBrute(store, {
freeRetries: 1000,
attachResetToRequest: false,
refreshTimeoutOnRequest: false,
minWait: 25*60*60*1000, // 1 day 1 hour (should never reach this wait time)
maxWait: 25*60*60*1000, // 1 day 1 hour (should never reach this wait time)
lifetime: 24*60*60, // 1 day (seconds not milliseconds)
failCallback: failCallback,
handleStoreError: handleStoreError
});

app.set('trust proxy', 1); // Don't set to "true", it's not secure. Make sure it matches your environment
app.post('/auth',
globalBruteforce.prevent,
userBruteforce.getMiddleware({
key: function(req, res, next) {
// prevent too many attempts for the same username
next(req.body.username);
}
}),
function (req, res, next) {
if (User.isValidLogin(req.body.username, req.body.password)) { // omitted for the sake of conciseness
// reset the failure counter so next time they log in they get 5 tries again before the delays kick in
req.brute.reset(function () {
res.redirect('/'); // logged in, send them to the home page
});
} else {
res.flash('error', "Invalid username or password")
res.redirect('/login'); // bad username/password, send them back to the login page
}
}
);
```

Behind a Proxy
--------------
If your application is behind a proxy (Apache, Nginx) you should not forget set the **trust proxy** param on your Express application.
```
app.set('trust proxy', 1);
```

Changelog
---------
### v1.0.1
* BUG: Fixed an edge case where freeretries weren't being respected if app servers had slightly different times

### v1.0.0
* NEW: Updated to use `Express` 4.x as a peer dependency.
* REMOVED: `proxyDepth` option on `ExpressBrute` has been removed. Use `app.set('trust proxy', x)` from Express 4 instead. [More Info](http://expressjs.com/en/guide/behind-proxies.html)
* REMOVED: `getIPFromRequest(req)` has been removed from instances, use `req.ip` instead.

### v0.6.0
* NEW: Added new ignoreIP option. (Thanks [Magnitus-](https://github.com/Magnitus-)!)
* CHANGED: `.reset` callbacks are now always called asyncronously, regardless of the implementation of the store (particularly effects `MemoryStore`).
* CHANGED: Unit tests have been converted from Jasmine to Mocha/Chai/Sinon
* BUG: Fixed a crash when .reset was called without a callback function

### v0.5.3
* NEW: Added the `handleStoreError` option to allow more customizable handling of errors that are thrown by the persistent store. Default behavior is to throw the errors as an exception - there is nothing ExpressBrute can do to recover.
* CHANGED: Errors thrown as a result of errors raised by the store now include the store's error as well, for debugging purposes.

### v0.5.2
* CHANGED: Stopped using res.send(status, body), as it is deprecated in express 4.x. Instead call res.status and res.send separately (Thanks marinewater!)

### v0.5.1
* BUG: When setting proxyDepth to 1, ips is never populated with proxied X-Forwarded-For IP.

### v0.5.0
* NEW: Added an additional `FailTooManyRequests` failure callback, that returns a 429 (TooManyRequests) error instead of 403 (Forbidden). This is a more accurate error status code.
* NEW: All the built in failure callbacks now set the "Retry-After" header to the number of seconds until it is safe to try again. Per [RFC6585](https://tools.ietf.org/html/rfc6585#section-4)
* NEW: Documentation updated to list some known store implementations.
* CHANGED: Default failure callback is now `FailTooManyRequests`. `FailForbidden` remains an option for backwards compatiblity.
* 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).
* CHANGED: `FailMark` no longer sets returns 403 Forbidden, instead does 429 TooManyRequets.

### v0.4.2
* 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.

### v0.4.1
* NEW: `refreshTimeoutOnRequest` option that allows you to prevent the remaining `lifetime` for a timer from being reset on each request (useful for implementing limits for set time frames, e.g. requests per day)
* BUG: Lifetimes were not previously getting extended properly for instances of `ExpressBrute.MemoryStore`

### v0.4.0
* NEW: `attachResetToRequest` parameter that lets you prevent the request object being decorated
* NEW: `failCallback` can be overriden by `getMiddleware`
* NEW: `proxyDepth` option on `ExpressBrute` that specifies how many levels of the `X-Forwarded-For` header to trust (inspired by [express-bouncer](https://github.com/dkrutsko/express-bouncer/)).
* NEW: `getIPFromRequest` method that essentially allows `reset` to used in a similar ways as in v0.2.2. This also respects the new `proxyDepth` setting.
* CHANGED: `getMiddleware` now takes an options object instead of the key directly.

### v0.3.0
* NEW: Support for using custom keys to group requests further (e.g. grouping login requests by username)
* NEW: Support for middleware from multiple instances of `ExpressBrute` on the same route.
* NEW: Tracking `lifetime` now has a reasonable default derived from the other settings for that instance of `ExpressBrute`
* NEW: Keys are now hashed before saving to a store, to prevent really long key names and reduce the possibility of collisions.
* NEW: There is now a convience method that gets attached to `req` object as `req.brute.reset`. It takes a single parameter (a callback), and will reset all the counters used by `ExpressBrute` middleware that was called for the current route.
* CHANGED: Tracking `lifetime` is now specified on `ExpressBrute` instead of `MemcachedStore`. This also means lifetime is now supported by MemoryStore.
* CHANGED: The function signature for `ExpressBrute.reset` has changed. It now requires an IP and key be passed instead of a request object.
* IMPROVED: Efficiency for large values of `freeRetries`.
* BUG: Removed a small chance of incorrectly triggering brute force protection.

0 comments on commit 010248e

Please sign in to comment.