New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

fastify-rate-limit

Package Overview
Dependencies
Maintainers
9
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fastify-rate-limit - npm Package Compare versions

Comparing version 2.1.1 to 2.2.0

example/example.js

8

index.d.ts
import * as http from 'http';
import * as fastify from 'fastify';
import * as ioredis from 'ioredis';
declare namespace fastifyRateLimit {
interface FastifyRateLimitOptions {
global?: boolean;
max?: number;

@@ -11,5 +11,5 @@ timeWindow?: number;

whitelist?: string[];
redis?: ioredis.Redis;
redis?: any;
skipOnError?: boolean;
keyGenerator?: (req: fastify.FastifyRequest<any>) => string | number;
keyGenerator?: (req: fastify.FastifyRequest<http.IncomingMessage>) => string | number;
}

@@ -25,2 +25,2 @@ }

export = fastifyRateLimit;
export = fastifyRateLimit;

@@ -19,45 +19,119 @@ 'use strict'

function rateLimitPlugin (fastify, opts, next) {
const timeWindow = typeof opts.timeWindow === 'string'
? ms(opts.timeWindow)
: typeof opts.timeWindow === 'number'
? opts.timeWindow
function rateLimitPlugin (fastify, settings, next) {
// create the object that will hold the "main" settings that can be shared during the build
// 'global' will define, if the rate limit should be apply by default on all route. default : true
const globalParams = {
global: (typeof settings.global === 'boolean') ? settings.global : true
}
// define the global maximum of request allowed
globalParams.max = (typeof settings.max === 'number' || typeof settings.max === 'function')
? settings.max
: 1000
// define the global Time Window
globalParams.timeWindow = typeof settings.timeWindow === 'string'
? ms(settings.timeWindow)
: typeof settings.timeWindow === 'number'
? settings.timeWindow
: 1000 * 60
const store = opts.redis
? new RedisStore(opts.redis, timeWindow)
: new LocalStore(timeWindow, opts.cache)
globalParams.whitelist = settings.whitelist || []
const keyGenerator = typeof opts.keyGenerator === 'function'
? opts.keyGenerator
// define the name of the app component. Related to redis, it will be use as a part of the keyname define in redis.
const pluginComponent = {
whitelist: globalParams.whitelist
}
if (settings.redis) {
pluginComponent.store = new RedisStore(settings.redis, 'fastify-rate-limit-', globalParams.timeWindow)
} else {
pluginComponent.store = new LocalStore(globalParams.timeWindow, settings.cache, fastify)
}
globalParams.keyGenerator = typeof settings.keyGenerator === 'function'
? settings.keyGenerator
: (req) => req.raw.ip
const skipOnError = opts.skipOnError === true
const max = opts.max || 1000
const whitelist = opts.whitelist || []
const after = ms(timeWindow, { long: true })
// onRoute add the preHandler rate-limit function if needed
fastify.addHook('onRoute', (routeOptions) => {
if (routeOptions.config) {
if (routeOptions.config.rateLimit && typeof routeOptions.config.rateLimit === 'object') {
const current = Object.create(pluginComponent)
current.store = pluginComponent.store.child(routeOptions)
// if the current endpoint have a custom rateLimit configuration ...
buildRouteRate(current, makeParams(routeOptions.config.rateLimit), routeOptions)
} else if (routeOptions.config.rateLimit === false) {
// don't apply any rate-limit
} else {
throw new Error('Unknown value for route rate-limit configuration')
}
} else if (globalParams.global) {
// if the plugin is set globally ( meaning that all the route will be 'rate limited' )
// As the endpoint, does not have a custom rateLimit configuration, use the global one.
buildRouteRate(pluginComponent, globalParams, routeOptions)
}
})
fastify.addHook('onRequest', onRateLimit)
// Merge the parameters of a route with the global ones
function makeParams (routeParams) {
const result = Object.assign({}, globalParams, routeParams)
if (typeof result.timeWindow === 'string') {
result.timeWindow = ms(result.timeWindow)
}
return result
}
function onRateLimit (req, res, next) {
var key = keyGenerator(req)
if (whitelist.indexOf(key) > -1) {
next()
}
function buildRouteRate (pluginComponent, params, routeOptions) {
const after = ms(params.timeWindow, { long: true })
if (Array.isArray(routeOptions.preHandler)) {
routeOptions.preHandler.push(preHandler)
} else if (typeof routeOptions.preHandler === 'function') {
routeOptions.preHandler = [routeOptions.preHandler, preHandler]
} else {
routeOptions.preHandler = [preHandler]
}
// PreHandler function that will be use for current endpoint been processed
function preHandler (req, res, next) {
// We retrieve the key from the generator. (can be the global one, or the one define in the endpoint)
const key = params.keyGenerator(req)
// whitelist doesn't apply any rate limit
if (pluginComponent.whitelist.indexOf(key) > -1) {
next()
} else {
store.incr(key, onIncr)
return
}
// As the key is not whitelist in redis/lru, then we increment the rate-limit of the current request and we call the function "onIncr"
pluginComponent.store.incr(key, onIncr)
function onIncr (err, current) {
if (err && skipOnError === false) return next(err)
if (err && params.skipOnError === false) {
return next(err)
}
if (current <= max) {
res.header('X-RateLimit-Limit', max)
res.header('X-RateLimit-Remaining', max - current)
if (current <= params.max) {
res.header('X-RateLimit-Limit', params.max)
res.header('X-RateLimit-Remaining', params.max - current)
if (typeof params.onExceeding === 'function') {
params.onExceeding(req)
}
next()
} else {
if (typeof params.onExceeded === 'function') {
params.onExceeded(req)
}
res.type('application/json').serializer(serializeError)
res.code(429)
.header('X-RateLimit-Limit', max)
.header('X-RateLimit-Limit', params.max)
.header('X-RateLimit-Remaining', 0)
.header('Retry-After', timeWindow)
.header('Retry-After', params.timeWindow)
.send({

@@ -71,4 +145,2 @@ statusCode: 429,

}
next()
}

@@ -75,0 +147,0 @@

{
"name": "fastify-rate-limit",
"version": "2.1.1",
"version": "2.2.0",
"description": "A low overhead rate limiter for your routes",

@@ -8,4 +8,4 @@ "main": "index.js",

"redis": "docker run -p 6379:6379 --rm redis:3.0.7",
"test": "standard && tap test.js && npm run typescript",
"typescript": "tsc --project ./tsconfig.json"
"test": "standard && tap --cov test/*.test.js && npm run typescript",
"typescript": "tsc --project ./test/types/tsconfig.json"
},

@@ -28,15 +28,21 @@ "repository": {

"devDependencies": {
"@types/ioredis": "^4.0.10",
"fastify": "^2.0.0",
"ioredis": "^4.2.0",
"@types/ioredis": "~4.0.11",
"@types/node": "~12.0.4",
"fastify": "^2.5.0",
"ioredis": "^4.9.0",
"standard": "^12.0.1",
"tap": "^12.1.0",
"typescript": "^3.3.3333"
"tap": "^12.6.6",
"typescript": "^3.4.2"
},
"dependencies": {
"fast-json-stringify": "^1.9.2",
"fast-json-stringify": "^1.14.0",
"fastify-plugin": "^1.3.0",
"ms": "^2.1.1",
"tiny-lru": "^6.0.1"
},
"greenkeeper": {
"ignore": [
"tap"
]
}
}
# fastify-rate-limit
[![Greenkeeper badge](https://badges.greenkeeper.io/fastify/fastify-rate-limit.svg)](https://greenkeeper.io/)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
[![Build Status](https://travis-ci.org/fastify/fastify-rate-limit.svg?branch=master)](https://travis-ci.org/fastify/fastify-rate-limit)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Build Status](https://travis-ci.org/fastify/fastify-rate-limit.svg?branch=master)](https://travis-ci.org/fastify/fastify-rate-limit)
A low overhead rate limiter for your routes. Supports Fastify `2.x` versions.

@@ -47,5 +47,7 @@

### Options
You can pass the following options during the plugin registration, the values will be used in all the routes.
You can pass the following options during the plugin registration:
```js
fastify.register(require('fastify-rate-limit'), {
global : false, // default true
max: 3, // default 1000

@@ -60,10 +62,14 @@ timeWindow: 5000, // default 1000 * 60

```
- `global` : indicates if the plugin should apply the rate limit setting to all routes within the encapsulation scope
- `max`: is the maximum number of requests a single client can perform inside a timeWindow.
- `timeWindow:` the duration of the time window, can be expressed in milliseconds (as a number) or as a string, see [`ms`](https://github.com/zeit/ms) too see the supported formats.
- `cache`: this plugin internally uses a lru cache to handle the clients, you can change the size of the cache with this option.
- `whitelist`: array of string of ips to exclude from rate limiting.
- `timeWindow:` the duration of the time window. It can be expressed in milliseconds or as a string (in the [`ms`](https://github.com/zeit/ms) format)
- `cache`: this plugin internally uses a lru cache to handle the clients, you can change the size of the cache with this option
- `whitelist`: array of string of ips to exclude from rate limiting
- `redis`: by default this plugins uses an in-memory store, which is fast but if you application works on more than one server it is useless, since the data is store locally.<br>
You can pass a Redis client here and magically the issue is solved. To achieve the maximum speed, this plugins requires the use of [`ioredis`](https://github.com/luin/ioredis).
You can pass a Redis client here and magically the issue is solved. To achieve the maximum speed, this plugins requires the use of [`ioredis`](https://github.com/luin/ioredis)
- `skipOnError`: if `true` it will skip errors generated by the storage (eg, redis not reachable).
- `keyGenerator`: a function to generate a unique identifier for each incoming request. Defaults to `(req) => req.ip`, the IP is resolved by fastify using `req.connection.remoteAddress` or `req.headers['x-forwarded-for']` if [trustProxy](https://www.fastify.io/docs/master/Server/#trustproxy) option is enabled. Use it if you want to override this behavior. Example usage:
- `keyGenerator`: a function to generate a unique identifier for each incoming request. Defaults to `(req) => req.ip`, the IP is resolved by fastify using `req.connection.remoteAddress` or `req.headers['x-forwarded-for']` if [trustProxy](https://www.fastify.io/docs/master/Server/#trustproxy) option is enabled. Use it if you want to override this behavior
`keyGenerator` example usage:
```js

@@ -81,2 +87,84 @@ fastify.register(require('fastify-rate-limit'), {

### Options on the endpoint itself
Rate limiting can be configured also for some routes, applying the configuration independently.
For example the `whitelist` if configured:
- on the plugin registration will affect all endpoints within the encapsulation scope
- on the route declaration will affect only the targeted endpoint
The global whitelist is configured when registering it with `fastify.register(...)`.
The endpoint whitelist is set on the endpoint directly with the `{ config : { rateLimit : { whitelist : [] } } }` object.
ACL checking is performed based on the value of the key from the `keyGenerator`.
In this example we are checking the IP address, but it could be a whitelist of specific user identifiers (like JWT or tokens):
```js
const fastify = require('fastify')()
fastify.register(require('fastify-rate-limit'),
{
global : false, // don't apply these settings to all the routes of the context
max: 3000, // default global max rate limit
whitelist: ['192.168.0.10'], // global whitelist access.
redis: redis, // custom connection to redis
})
// add a limited route with this configuration plus the global one
fastify.get('/', {
config: {
rateLimit: {
max: 3,
timeWindow: '1 minute'
}
}
}, (req, reply) => {
reply.send({ hello: 'from ... root' })
})
// add a limited route with this configuration plus the global one
fastify.get('/private', {
config: {
rateLimit: {
max: 3,
timeWindow: '1 minute'
}
}
}, (req, reply) => {
reply.send({ hello: 'from ... private' })
})
// this route doesn't have any rate limit
fastify.get('/public', (req, reply) => {
reply.send({ hello: 'from ... public' })
})
// add a limited route with this configuration plus the global one
fastify.get('/public/sub-rated-1', {
config: {
rateLimit: {
timeWindow: '1 minute',
whitelist: ['127.0.0.1'],
onExceeding: function (req) {
console.log('callback on exceededing ... executed before response to client')
},
onExceeded: function (req) {
console.log('callback on exceeded ... to black ip in security group for example, req is give as argument')
}
}
}
}, (req, reply) => {
reply.send({ hello: 'from sub-rated-1 ... using default max value ... ' })
})
```
In the route creation you can override the same settings of the plugin registration plus the additionals options:
- `onExceeding` : callback that will be executed each time a request is made to a route that is rate limited
- `onExceeded` : callback that will be executed when a user reached the maximum number of tries. Can be useful to blacklist clients
<a name="license"></a>

@@ -83,0 +171,0 @@ ## License

'use strict'
const lru = require('tiny-lru')
const ms = require('ms')
function LocalStore (timeWindow, cache) {
function LocalStore (timeWindow, cache, app) {
this.lru = lru(cache || 5000)
setInterval(this.lru.clear.bind(this.lru), timeWindow).unref()
this.interval = setInterval(this.lru.clear.bind(this.lru), timeWindow).unref()
this.app = app
app.addHook('onClose', (done) => {
clearInterval(this.interval)
})
}

@@ -16,2 +22,11 @@

LocalStore.prototype.child = function (routeOptions) {
let timeWindow = routeOptions.config.rateLimit.timeWindow
if (typeof timeWindow === 'string') {
timeWindow = ms(timeWindow)
}
return new LocalStore(timeWindow, routeOptions.config.rateLimit.cache, this.app)
}
module.exports = LocalStore
'use strict'
const ms = require('ms')
const noop = () => {}
function RedisStore (redis, timeWindow) {
function RedisStore (redis, key, timeWindow) {
this.redis = redis
this.timeWindow = timeWindow
this.key = 'fastify-rate-limit-'
this.key = key
}

@@ -26,2 +27,13 @@

RedisStore.prototype.child = function (routeOptions) {
let timeWindow = routeOptions.config.rateLimit.timeWindow
if (typeof timeWindow === 'string') {
timeWindow = ms(timeWindow)
}
const child = Object.create(this)
child.key = this.key + routeOptions.method + routeOptions.url + '-'
child.timeWindow = timeWindow
return child
}
module.exports = RedisStore
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc