Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fastify-rate-limit

Package Overview
Dependencies
Maintainers
1
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 0.2.0 to 0.3.0

store/LocalStore.js

68

index.js
'use strict'
const fp = require('fastify-plugin')
const lru = require('tiny-lru')
const FJS = require('fast-json-stringify')
const ms = require('ms')
const LocalStore = require('./store/LocalStore')
const RedisStore = require('./store/RedisStore')
const serializeError = FJS({

@@ -18,15 +20,17 @@ type: 'object',

function rateLimitPlugin (fastify, opts, next) {
const cache = lru(opts.cache || 5000)
const max = opts.max || 1000
const whitelist = opts.whitelist || []
const timeWindow = typeof opts.timeWindow === 'string'
? ms(opts.timeWindow)
: typeof opts.timeWindow === 'number'
? opts.timeWindow
: 1000 * 60
? opts.timeWindow
: 1000 * 60
const store = opts.redis
? new RedisStore(opts.redis, timeWindow)
: new LocalStore(timeWindow, opts.cache)
const skipOnError = opts.skipOnError === true
const max = opts.max || 1000
const whitelist = opts.whitelist || []
const after = ms(timeWindow, { long: true })
const interval = setInterval(cache.reset.bind(cache), timeWindow)
if (interval.unref) interval.unref()
fastify.addHook('onRequest', onRateLimit)

@@ -36,25 +40,29 @@

var ip = req.headers['X-Forwarded-For'] || req.connection.remoteAddress
var current = cache.get(ip) || 0
if (whitelist.indexOf(ip) > -1) {
next()
} else {
store.incr(ip, onIncr)
}
var limitReached = current >= max
function onIncr (err, current) {
if (err && skipOnError === false) return next(err)
if (whitelist.indexOf(ip) === -1) {
if (limitReached === false) current++
cache.set(ip, current)
res.setHeader('X-RateLimit-Limit', max)
res.setHeader('X-RateLimit-Remaining', max - current)
if (current <= max) {
res.setHeader('X-RateLimit-Limit', max)
res.setHeader('X-RateLimit-Remaining', max - current)
next()
} else {
res.writeHead(429, {
'X-RateLimit-Limit': max,
'X-RateLimit-Remaining': 0,
'Content-Type': 'application/json',
'Retry-After': timeWindow
})
res.end(serializeError({
statusCode: 429,
error: 'Too Many Requests',
message: `Rate limit exceeded, retry in ${after}`
}))
}
}
if (limitReached === false) {
next()
} else {
res.setHeader('Content-Type', 'application/json')
res.setHeader('Retry-After', timeWindow)
res.statusCode = 429
res.end(serializeError({
statusCode: 429,
error: 'Too Many Requests',
message: `Rate limit exceeded, retry in ${after}`
}))
}
}

@@ -66,4 +74,4 @@

module.exports = fp(rateLimitPlugin, {
fastify: '>=0.43.0',
fastify: '>=1.x',
name: 'fastify-rate-limit'
})
{
"name": "fastify-rate-limit",
"version": "0.2.0",
"version": "0.3.0",
"description": "A low overhead rate limiter for your routes",
"main": "index.js",
"scripts": {
"redis": "docker run -p 6379:6379 --rm redis:3.0.7",
"test": "standard && tap test.js"

@@ -25,12 +26,13 @@ },

"devDependencies": {
"fastify": "^1.1.1",
"standard": "^10.0.3",
"tap": "^11.1.0"
"fastify": "^1.2.1",
"ioredis": "^3.2.2",
"standard": "^11.0.1",
"tap": "^11.1.3"
},
"dependencies": {
"fast-json-stringify": "^1.1.0",
"fast-json-stringify": "^1.2.0",
"fastify-plugin": "^0.2.2",
"ms": "^2.1.1",
"tiny-lru": "^1.5.0"
"tiny-lru": "^1.5.2"
}
}

@@ -50,3 +50,5 @@ # fastify-rate-limit

cache: 10000, // default 5000
whitelist: ['127.0.0.1'] // default []
whitelist: ['127.0.0.1'], // default []
redis: new Redis({ host: '127.0.0.1' }), // default null
skipOnError: true // default false
})

@@ -58,2 +60,5 @@ ```

- `whitelist`: array of string of ips to exlude 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).
- `skipOnError`: if `true` it will skip errors generated by the storage (eg, redis not reachable)

@@ -60,0 +65,0 @@ <a name="license"></a>

@@ -5,4 +5,6 @@ 'use strict'

const test = t.test
const Redis = require('ioredis')
const Fastify = require('fastify')
const rateLimit = require('./index')
const noop = () => {}

@@ -131,1 +133,99 @@ test('Basic', t => {

})
test('With redis store', t => {
t.plan(19)
const fastify = Fastify()
const redis = new Redis({ host: '127.0.0.1' })
fastify.register(rateLimit, {
max: 2,
timeWindow: 1000,
redis: redis
})
fastify.get('/', (req, reply) => {
reply.send('hello!')
})
fastify.inject('/', (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 200)
t.strictEqual(res.headers['x-ratelimit-limit'], 2)
t.strictEqual(res.headers['x-ratelimit-remaining'], 1)
fastify.inject('/', (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 200)
t.strictEqual(res.headers['x-ratelimit-limit'], 2)
t.strictEqual(res.headers['x-ratelimit-remaining'], 0)
fastify.inject('/', (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 429)
t.strictEqual(res.headers['content-type'], 'application/json')
t.strictEqual(res.headers['x-ratelimit-limit'], 2)
t.strictEqual(res.headers['x-ratelimit-remaining'], 0)
t.strictEqual(res.headers['retry-after'], 1000)
t.deepEqual({
statusCode: 429,
error: 'Too Many Requests',
message: 'Rate limit exceeded, retry in 1 second'
}, JSON.parse(res.payload))
setTimeout(retry, 1100)
})
})
})
function retry () {
fastify.inject('/', (err, res) => {
redis.flushall(noop)
redis.quit(noop)
t.error(err)
t.strictEqual(res.statusCode, 200)
t.strictEqual(res.headers['x-ratelimit-limit'], 2)
t.strictEqual(res.headers['x-ratelimit-remaining'], 1)
})
}
})
test('Skip on redis error', t => {
t.plan(13)
const fastify = Fastify()
const redis = new Redis({ host: '127.0.0.1' })
fastify.register(rateLimit, {
max: 2,
timeWindow: 1000,
redis: redis,
skipOnError: true
})
fastify.get('/', (req, reply) => {
reply.send('hello!')
})
fastify.inject('/', (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 200)
t.strictEqual(res.headers['x-ratelimit-limit'], 2)
t.strictEqual(res.headers['x-ratelimit-remaining'], 1)
redis.flushall(noop)
redis.quit(err => {
t.error(err)
fastify.inject('/', (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 200)
t.strictEqual(res.headers['x-ratelimit-limit'], 2)
t.strictEqual(res.headers['x-ratelimit-remaining'], 2)
fastify.inject('/', (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 200)
t.strictEqual(res.headers['x-ratelimit-limit'], 2)
t.strictEqual(res.headers['x-ratelimit-remaining'], 2)
})
})
})
})
})

Sorry, the diff of this file is not supported yet

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