@risingstack/protect
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -6,3 +6,6 @@ 'use strict' | ||
const bodyParser = require('body-parser') | ||
const redis = require('redis') | ||
const client = redis.createClient() | ||
const app = express() | ||
@@ -13,2 +16,3 @@ | ||
})) | ||
app.use(protect.express.sqlInjection({ | ||
@@ -18,2 +22,3 @@ body: true, | ||
})) | ||
app.use(protect.express.xss({ | ||
@@ -24,2 +29,7 @@ body: true, | ||
app.use(protect.express.rateLimiter({ | ||
db: client, | ||
id: (request) => request.connection.remoteAddress | ||
})) | ||
app.get('/', (request, response) => { | ||
@@ -29,2 +39,17 @@ response.send('hello protect!') | ||
app.get('/some-page', protect.express.rateLimiter({ | ||
db: client, | ||
id: (request) => request.connection.remoteAddress, | ||
max: 10 | ||
}), (request, response) => { | ||
response.send('ok') | ||
}) | ||
app.post('/login', protect.express.rateLimiter({ | ||
db: client, | ||
id: (request) => request.body.email | ||
}), (request, response) => { | ||
response.send('wuut logged in') | ||
}) | ||
app.listen(process.env.PORT || 3000, (err) => { | ||
@@ -31,0 +56,0 @@ if (err) { |
'use strict' | ||
const debug = require('debug')('@risingstack/protect:express') | ||
const Limiter = require('ratelimiter') | ||
const rules = require('../rules') | ||
@@ -74,2 +75,44 @@ | ||
function rateLimiter (options = {}) { | ||
const { loggerFunction = noop } = options | ||
if (!options.db) { | ||
throw new Error('options.db is required') | ||
} | ||
if (!options.id) { | ||
throw new Error('options.id is required') | ||
} | ||
return (request, response, next) => { | ||
const limiter = new Limiter({ | ||
db: options.db, | ||
id: options.id(request), | ||
max: options.max, | ||
duration: options.duration | ||
}) | ||
limiter.get((err, limit) => { | ||
if (err) { | ||
loggerFunction('ratelimiter-error', err) | ||
return next(err) | ||
} | ||
response.set('RateLimit-Limit', limit.total) | ||
response.set('RateLimit-Remaining', limit.remaining - 1) | ||
response.set('RateLimit-Reset', limit.reset) | ||
if (limit.remaining) { | ||
return next() | ||
} | ||
const after = Math.floor(limit.reset - (Date.now() / 1000)) | ||
response.set('Retry-After', after) | ||
response.sendStatus(429) | ||
return next() | ||
}) | ||
} | ||
} | ||
function noop () {} | ||
@@ -79,3 +122,4 @@ | ||
sqlInjection, | ||
xss | ||
xss, | ||
rateLimiter | ||
} |
@@ -6,3 +6,5 @@ 'use strict' | ||
const bodyParser = require('body-parser') | ||
const redis = require('redis') | ||
const client = redis.createClient() | ||
const lib = require('../') | ||
@@ -102,3 +104,3 @@ | ||
describe('together', () => { | ||
describe('together the xss and sqlInjection middleware', () => { | ||
it('works', () => { | ||
@@ -125,2 +127,45 @@ const app = express() | ||
}) | ||
describe('the ratelimiter', () => { | ||
it('adds the rate-limiter headers', () => { | ||
const app = express() | ||
app.use(lib.express.rateLimiter({ | ||
db: client, | ||
id: () => Math.floor(Math.random() * 10000) | ||
})) | ||
app.get('/', (req, res) => { | ||
res.send('ok') | ||
}) | ||
return request(app) | ||
.get('/') | ||
.expect(200) | ||
.expect('RateLimit-Limit', '2500') | ||
.expect('RateLimit-Remaining', '2499') | ||
}) | ||
it('returns with 429 if ratelimited', () => { | ||
const app = express() | ||
const identifier = Math.floor(Math.random() * 10000) | ||
app.use(lib.express.rateLimiter({ | ||
db: client, | ||
id: () => identifier, | ||
max: 1, | ||
})) | ||
app.get('/', (req, res) => { | ||
res.send('ok') | ||
}) | ||
return request(app) | ||
.get('/') | ||
.expect(200) | ||
.expect('RateLimit-Limit', '1') | ||
.expect('RateLimit-Remaining', '0') | ||
.then(() => { | ||
request(app) | ||
.get('/') | ||
.expect(429) | ||
}) | ||
}) | ||
}) | ||
}) |
{ | ||
"name": "@risingstack/protect", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "", | ||
@@ -10,3 +10,4 @@ "main": "index.js", | ||
"lint": "eslint lib", | ||
"test": "npm run lint && npm run test-only" | ||
"test": "npm run lint && npm run test-only", | ||
"semantic-release": "semantic-release pre && npm publish && semantic-release post" | ||
}, | ||
@@ -42,2 +43,4 @@ "contributors": [ | ||
"pre-commit": "1.2.2", | ||
"redis": "2.7.1", | ||
"semantic-release": "^6.3.6", | ||
"sinon": "2.2.0", | ||
@@ -49,4 +52,5 @@ "sinon-chai": "2.10.0", | ||
"dependencies": { | ||
"debug": "2.6.6" | ||
"debug": "2.6.6", | ||
"ratelimiter": "3.0.3" | ||
} | ||
} | ||
} |
## Protect by RisingStack | ||
[![Build Status](https://travis-ci.org/RisingStack/protect.svg?branch=master)](https://travis-ci.org/RisingStack/protect) | ||
The purpose of this module is to provide out-of-box, proactive protection for common security problems, like | ||
@@ -10,2 +12,4 @@ SQL injection attacks or XSS attacks. | ||
This is not a substitute for parameterized queries in SQL - just an extra layer of security. | ||
### Basic usage | ||
@@ -50,1 +54,23 @@ | ||
* default: `noop` | ||
#### `protect.express.rateLimiter([options])` | ||
Returns an Express middleware, which ratelimits | ||
* `options.id`: function that returns the id used for ratelimiting - gets the `request` as its' first parameter | ||
* required | ||
* example: `(request) => request.connection.remoteAddress` | ||
* `options.db`: redis connection instance | ||
* required | ||
* `options.max`: max requests within `options.duration` | ||
* default: 2500 | ||
* `options.max`: of limit in milliseconds | ||
* default: 3600000 | ||
* `options.loggerFunction`: you can provide a logger function for the middleware to log attacks | ||
* default: `noop` | ||
### Roadmap | ||
* block security scanners | ||
* schell / code injection protection | ||
* [what would you add?](https://github.com/RisingStack/protect/issues) |
17251
17
386
75
2
17
+ Addedratelimiter@3.0.3
+ Addedratelimiter@3.0.3(transitive)