Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

hapi-rate-limitor

futurestudio3.7kMIT4.0.0TypeScript support: included

Rate limiting for hapi/hapi.js to prevent brute-force attacks

brute force, brute force protection, bruteforce, hapi, hapi.js, hapijs, limit, plugin, rate, rate limit, rate limiter, rate limiting, rate-limit

readme

hapi-rate-limitor logo

Solid and easy to use rate limiting for hapi.


Installation · Usage · Plugin Options · Route Options · Response Headers



Build Status Known Vulnerabilities Latest Version Total downloads

Follow @marcuspoehls for updates!


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')
})

// went smooth like chocolate with default settings :)

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,                                     // a maximum of 2 requests
    duration: 1000                              // per second (the value is in milliseconds),
    userAttribute: 'id',
    userLimitAttribute: 'rateLimit',
    view: 'rate-limit-exceeded',                // render this view when the rate limit exceeded
    enabled: true
    skip: async (request) => {
      return request.path.includes('/admin')    // example: disable rate limiting for the admin panel
    },
    ipWhitelist: ['1.1.1.1'],                   // list of IP addresses skipping rate limiting
    getIp: async (request) => {                 // manually determine the requesting IP address
      const ips = request.headers['x-forwarded-for'].split(',')

      return ips[ips.length - 1]
    },
    emitter: yourEventEmitter,                  // your event emitter instance
  }
})

// went smooth like chocolate :)

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'
    // ... etc
  }
})

// went smooth like chocolate :)

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 request
  • rate-limit:in-quota: after rate-limiting and only if the request’s limit is in the quota
  • rate-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 => {
  // handle rate-limiting exceeded
})

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

    // … other plugin options
  }
})

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,             // a maximum of 60 requests
    duration: 60 * 1000, // per minute (the value is in milliseconds)
  }
})

await server.route({
  method: 'POST',
  path: '/login',
  options: {
    handler: () {
      // do the login handling
    },
    plugins: {
      'hapi-rate-limitor': {
        max: 5,              // a maximum of 5 requests
        duration: 60 * 1000, // per minute
        enabled: false       // but it’s actually not enabled ;-)
      }
    }
  }
})

// went smooth like chocolate :)

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,                          // a maximum of 500 requests (default is 2500)
    duration: 60 * 60 * 1000           // per hour (the value is in milliseconds)
    // … other plugin options
  }
})

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:

/**
 * the authenticated user object may contain a custom rate limit attribute.
 * In this case, it’s called "rateLimit".
 */
request.auth.credentials = {
  id: 'custom-uuid',
  rateLimit: 1750,
  name: 'Marcus'
  // … further attributes
}

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.

Response Headers

The plugin sets the following response headers:

  • X-Rate-Limit-Limit: total request limit (max) within duration
  • X-Rate-Limit-Remaining: remaining quota until reset
  • X-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.

Contributing

  1. Create a fork
  2. Create your feature branch: git checkout -b my-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin my-new-feature
  5. Submit a pull request 🚀

License

MIT © Future Studio


futurestud.io  ·  GitHub @futurestudio  ·  Twitter @futurestud_io

changelog

Changelog

4.0.0 - 2024-03-25

Added

  • call h.continue after adding the rate-limit related response headers: this proceeds the plugin chain and plugins registered after hapi-rate-limitor can do their handling as well

Updated

  • bump dependencies

Breaking Changes

This release drops support for Node.js v12. Please use Node.js v14 and later.

3.1.2 - 2022-02-15

Updated

  • bump dependencies
  • minor code refinements
  • great to see a release after 1.5 years of silence 🥳

3.1.1 - 2020-08-05

Updated

  • bump dependencies
  • minor code refinements
  • replaced request-ip dependency with @supercharge/request-ip providing improved request IP detection

3.1.0 - 2020-06-01

Updated

  • refined route-specific rate limit handling
  • bump dependencies

Possible Breaking Changes

This release introduces an updated handling route-level max attempts.

Previously, the default (server-wide) rate limit affected route-level rate limits. Now, the route-level rate limits are independend and not affected by the default rate limit.

Example: you have a /login route with { max: 10 } configuration and your default configuration is { max: 60 }. In the previous version, any request to other pages than /login would affect the max limit of 10 requests for the /login route. This behavior may have eaten all 10 requests already before even visiting the /login route. This new version handles the /login route independently from other pages because it has its own max configuration.

This changed handling may introduce a breaking change for your app if you previously worked around that issue. Sorry, if I’m causing you trouble. I’m releasing this version as a minor release in the 2.x and 3.x release lines. In case you’re using tilde (~) in your package.json file, you’re not directly updated to this version when running npm install.

3.0.0 - 2020-01-10

Updated

  • bump dependencies
  • refined description in package.json

Breaking Changes

  • require Node.js v12
    • this change aligns with the hapi ecosystem requiring Node.js v12 with the release of hapi 19

2.13.0 - 2020-06-01

Updated

  • refined route-specific rate limit handling

Possible Breaking Changes

This release introduces an updated handling route-level max attempts.

Previously, the default (server-wide) rate limit affected route-level rate limits. Now, the route-level rate limits are independend and not affected by the default rate limit.

Example: you have a /login route with { max: 10 } configuration and your default configuration is { max: 60 }. In the previous version, any request to other pages than /login would affect the max limit of 10 requests for the /login route. This behavior may have eaten all 10 requests already before even visiting the /login route. This new version handles the /login route independently from other pages because it has its own max configuration.

This changed handling may introduce a breaking change for your app if you previously worked around that issue. Sorry, if I’m causing you trouble. I’m releasing this version as a minor release in the 2.x and 3.x release lines. In case you’re using tilde (~) in your package.json file, you’re not directly updated to this version when running npm install.

2.12.0 - 2019-11-22

Added

  • Travis testing for Node v13
  • TypeScript definitions for the rate limit request decoration and plugin options: this allows autocompletion in your editor (at least in VS Code :))

Updated

  • bump dependencies
  • internal refactorings: move event emitter to a dedicated class
  • internal refactorings: move rate limit data to a dedicated class

Removed

  • lodash as a dependency
  • @hapi/hoek as a devDependency

2.11.0 - 2019-10-17

Added

  • basic TypeScript declarations in lib/index.d.ts

2.10.0 - 2019-10-10

Added

  • getIp option allowing you to manually determine the IP address from the request.

    • Example:

      await server.register({
        plugin: require('hapi-rate-limitor'),
        options: {
          getIp: async (request) => {
            const ips = request.headers['x-forwarded-for'].split(',')
      
            return ips[ips.length - 1]
          }
        }
      }
  • emitter option to pass in your custom event emitter
  • dispatch rate limiting events: rate-limit:attempt, rate-limit:in-quota, rate-limit:exceeded
    • every event listener receives the request as the only argument

2.9.0 - 2019-08-13

Added

  • add ipWhitelist option representing an array of IP addresses that will skip rate limiting

Updated

  • bump dependencies
  • update NPM scripts
  • minor code refinements

Removed

  • Travis testing for Node.js version 11

2.8.0 - 2019-06-25

Added

  • support for Redis connection string, like redis: 'redis://user:pass@dokku-redis-lolipop:6379' (Thank you Rob! PR #37)

Updated

  • minor code refinements
  • bump dependencies

2.7.1 - 2019-05-10

Updated

  • update to @hapi/boom from boom
  • test Node.js v12
  • bump dependencies

2.7.0 - 2019-05-04

Added

  • ensure a user-defined view exists on server start, otherwise throw an error

Updated

  • bump dependencies
  • minor internal refactorings

2.6.1 - 2019-04-27

Updated

  • bump dependencis
  • update to hapi scoped dependencies

2.6.0 - 2019-02-28

Added

  • wait for Redis connection onPreStart
  • close Redis connection onPostStop

2.5.3 - 2019-02-18

Updated

  • bump dependencies
  • fix badges in Readme
  • Changelog: rename GitHub references fs-opensource -> futurestudio

2.5.2 - 2019-01-26

Updated

  • Readme: rename GitHub references fs-opensource -> futurestudio

2.5.1 - 2019-01-22

Updated

  • update tests for hapi 18
  • bump dependencies

2.5.0 - 2019-01-16

Added

  • plugin option skip: a function that determines whether to skip rate limiting for a request

Updated

  • bump dependencies

2.4.0 - 2018-12-12

Added

Updated

  • bump dependencies
  • refined plugin options overview in Readme
  • improved formatting of code examples in Readme

2.3.0 - 2018-10-29

Added

  • enabled plugin option: allows you to disable the plugin, e.g. when running tests
  • enabled route option: disable the plugin for individual routes that would eat up the user’s rate limit, e.g. assets

Updated

  • test for Node.js 11

2.2.0 - 2018-10-21

Updated

  • extract ID from authenticated requests even without user limit
  • extract user limit even without user identifier
  • apply user’s max on routes with rate limit config
  • bump dependencies

2.1.0 - 2018-09-30

Added

Updated

  • refactoring: move rate limit handling to class
  • fix lint issues in test files
  • bump dependencies

Deleted

  • Travis testing for Node.js v9

2.0.1 - 2018-09-11

Updated

  • fix 404 handling: proceed response without rate limit data

2.0.0 - 2018-09-11

Added

Updated

  • fix user-specific rate limits and use the userId as identifier
  • switch from lab and code to AVA for testing

Deleted

  • unused .prettierignore file

Breaking Changes

  • userLimitKey becomes userLimitAttribute in 2.0: if you used dynamic rate limits with userLimitKey, you need to change it to userLimitAttribute.

1.1.1 - 2018-08-21

Updated

  • Readme: quick navigation and logo size fix for small screens

1.1.0 - 2018-08-08

Added

1.0.0 - 2018-07-11

Added

  • 1.0.0 release 🚀 🎉