The Future Studio University supports development of this hapi plugin 🚀
Join the Future Studio University and Skyrocket in Node.js
Introduction
A hapi plugin to prevent brute-force attacks in your app. The rate limiter uses Redis to store rate-limit related data.
hapi-rate-limitor
is built on top of these solid and awesome projects:
Each package solves its own problem perfectly. hapi-rate-limitor
composes the solutions of each problem to a solid rate limit plugin for hapi.
Requirements
hapi v19 (or later) and Node.js v12 (or newer)
This plugin requires hapi v19 (or later) and Node.js v12 or newer.
Compatibility
Major Release | hapi.js version | Node.js version |
---|
v3 | >=17 hapi | >=12 |
v2 | >=17 hapi | >=8 |
Installation
Add hapi-rate-limitor
as a dependency to your project:
npm i hapi-rate-limitor
Using hapi v18 or lower?
Use the 2.x
release line:
npm i hapi-rate-limitor@2
Usage
The most straight forward to use hapi-rate-limitor
is to register it to your hapi server.
This will use the default configurations of async-ratelimiter
and ioredis.
await server.register({
plugin: require('hapi-rate-limitor')
})
Plugin Options
Customize the plugin’s default configuration with the following options:
max
: Integer, default: 60
- the maximum number of requests allowed in a
duration
duration
: Integer, default: 60000
(1 minute)
- the lifetime window keeping records of a request in milliseconds
namespace
: String, default: 'hapi-rate-limitor'
- the used prefix to create the rate limit identifier before storing the data
redis
: Object, default: undefined
- the
redis
configuration property will be passed through to ioredis
creating your custom Redis client
extensionPoint
: String, default: 'onPostAuth'
userAttribute
: String, default: 'id'
- the property name identifying a user (credentials) for dynamic rate limits. This option is used to access the value from
request.auth.credentials
.
userLimitAttribute
: String, default: 'rateLimit'
- the property name identifying the rate limit value on dynamic rate limit. This option is used to access the value from
request.auth.credentials
.
view
: String, default: undefined
- view path to render the view instead of throwing an error (this uses
h.view(yourView, { total, remaining, reset }).code(429)
)
enabled
: Boolean, default: true
- a shortcut to enable or disable the plugin, e.g. when running tests
skip
: Function, default: () => false
- an async function with the signature
async (request)
to determine whether to skip rate limiting for a given request. The skip
function retrieves the incoming request as the only argument
ipWhitelist
: Array, default: []
- an array of whitelisted IP addresses that won’t be rate-limited. Requests from such IPs proceed the request lifecycle. Notice that the related responses won’t contain rate limit headers.
getIp
: Function, default: undefined
- an async function with the signature
async (request)
to manually determine the requesting IP address. This is helpful if your load balancer provides the client IP address as the last item in the list of forwarded addresses (e.g. Heroku and AWS ELB)
emitter
: Object, default: server.events
All other options are directly passed through to async-ratelimiter.
await server.register({
plugin: require('hapi-rate-limitor'),
options: {
redis: {
port: 6379,
host: '127.0.0.1'
},
extensionPoint: 'onPreAuth',
namespace: 'hapi-rate-limitor',
max: 2,
duration: 1000
userAttribute: 'id',
userLimitAttribute: 'rateLimit',
view: 'rate-limit-exceeded',
enabled: true
skip: async (request) => {
return request.path.includes('/admin')
},
ipWhitelist: ['1.1.1.1'],
getIp: async (request) => {
const ips = request.headers['x-forwarded-for'].split(',')
return ips[ips.length - 1]
},
emitter: yourEventEmitter,
}
})
You can also use a Redis connection string.
await server.register({
plugin: require('hapi-rate-limitor'),
options: {
redis: 'redis://lolipop:SOME_PASSWORD@dokku-redis-lolipop:6379',
extensionPoint: 'onPreAuth',
namespace: 'hapi-rate-limitor'
}
})
Please check the async-ratelimiter API for all options.
Events
hapi-rate-limitor
dispatches the following three events in the rate-limiting lifecycle:
rate-limit:attempt
: before rate-limiting the requestrate-limit:in-quota
: after rate-limiting and only if the request’s limit is in the quotarate-limit:exceeded
: after rate-limiting and only if the request’s quota is exceeded
Each event listener receives the related request as the only parameter. Here’s a sample listener:
emitter.on('rate-limit:exceeded', request => {
})
You can pass your own event emitter
instance as a config property while registering the hapi-rate-limitor
plugin to your hapi server. By default, hapi-rate-limitor
uses hapi’s server as an event emitter.
const EventEmitter = require('events')
const myEmitter = new EventEmitter()
await server.register({
plugin: require('hapi-rate-limitor'),
options: {
emitter: myEmitter
}
})
Route Options
Customize the plugin’s default configuration on routes. A use case for this is a login route where you want to reduce the request limit even lower than the default limit.
On routes, hapi-rate-limitor
respects all options related to rate limiting. Precisely, all options that async-ratelimiter supports. It does not accept Redis connection options or identifiers for dynamic rate limiting.
All other options are directly passed through to async-ratelimiter.
await server.register({
plugin: require('hapi-rate-limitor'),
options: {
redis: {
port: 6379,
host: '127.0.0.1'
},
namespace: 'hapi-rate-limitor',
max: 60,
duration: 60 * 1000,
}
})
await server.route({
method: 'POST',
path: '/login',
options: {
handler: () {
},
plugins: {
'hapi-rate-limitor': {
max: 5,
duration: 60 * 1000,
enabled: false
}
}
}
})
Please check the async-ratelimiter API for all options.
Dynamic Rate Limits
To make use of user-specific rate limits, you need to configure the userAttribute
and userLimitAttribute
attributes in the hapi-rate-limitor
options.
These attributes are used to determine the rate limit for an authenticated user. The userAttribute
is the property name that uniquely identifies a user. The userLimitAttribute
is the property name that contains the rate limit value.
await server.register({
plugin: require('hapi-rate-limitor'),
options: {
userAttribute: 'id',
userLimitAttribute: 'rateLimit',
max: 500,
duration: 60 * 60 * 1000
}
})
This will calculate the maximum requests individually for each authenticated user based on the user’s id
and 'rateLimit'
attributes. Imagine the following user object as an authenticated user:
request.auth.credentials = {
id: 'custom-uuid',
rateLimit: 1750,
name: 'Marcus'
}
For this specific user, the maximum amount of requests is 1750
per hour (and not the plugin’s default 500
).
hapi-rate-limitor
uses the plugin’s limit if the request is unauthenticated or request.auth.credentials
doesn’t contain a rate-limit-related attribute.
The plugin sets the following response headers:
X-Rate-Limit-Limit
: total request limit (max
) within duration
X-Rate-Limit-Remaining
: remaining quota until resetX-Rate-Limit-Reset
: time since epoch in seconds that the rate limiting period will end
Feature Requests
Do you miss a feature? Please don’t hesitate to
create an issue with a short description of your desired addition to this plugin.
Links & Resources
Contributing
- Create a fork
- Create your feature branch:
git checkout -b my-feature
- Commit your changes:
git commit -am 'Add some feature'
- Push to the branch:
git push origin my-new-feature
- Submit a pull request 🚀
License
MIT © Future Studio
futurestud.io ·
GitHub @futurestudio ·
Twitter @futurestud_io