npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

koa2-ratelimit-ioredis

v0.0.6

Published

IP rate-limiting middleware for Koajs 2. Use to limit repeated requests to APIs and/or endpoints such as password reset.

Downloads

432

Readme

Koajs 2 Rate Limit ioredis

Rate-limiting middleware for Koa2 with async await. Use to limit repeated requests to APIs and/or endpoints such as password reset.

Note: This module is based on express-rate-limit and adapted to koa2 ES6 with the async await capabilities.

Summary

Install

$ npm install --save koa2-ratelimit-ioredis

Usage

For an API-only server where the rate-limiter should be applied to all requests:

const RateLimit = require('koa2-ratelimit-ioredis').RateLimit;

const limiter = RateLimit.middleware({
  interval: { min: 15 }, // 15 minutes = 15*60*1000
  max: 100, // limit each IP to 100 requests per interval
});

//  apply to all requests
app.use(limiter);

Create multiple instances to apply different rules to different routes:

const RateLimit = require('koa2-ratelimit-ioredis').RateLimit;
const KoaRouter = require('koa-router');
const router = new KoaRouter();

const getUserLimiter = RateLimit.middleware({
  interval: 15*60*1000, // 15 minutes
  max: 100,
  prefixKey: 'get/user/:id' // to allow the bdd to Differentiate the endpoint 
});
// add route with getUserLimiter middleware
router.get('/user/:id', getUserLimiter, (ctx) => {
  // Do your job
});

const createAccountLimiter = RateLimit.middleware({
  interval: { hour: 1, min: 30 }, // 1h30 window
  delayAfter: 1, // begin slowing down responses after the first request
  timeWait: 3*1000, // slow down subsequent responses by 3 seconds per request
  max: 5, // start blocking after 5 requests
  prefixKey: 'post/user', // to allow the bdd to Differentiate the endpoint 
  message: "Too many accounts created from this IP, please try again after an hour"
});
// add route  with createAccountLimiter middleware
router.post('/user', createAccountLimiter, (ctx) => {
  // Do your job
});

// mount routes
app.use(router.middleware())

Set default options to all your middleware:

const RateLimit = require('koa2-ratelimit-ioredis').RateLimit;

RateLimit.defaultOptions({
    message: 'Get out.',
    // ...
});

const getUserLimiter = RateLimit.middleware({
  max: 100,
  // message: 'Get out.', will be added
});

const createAccountLimiter = RateLimit.middleware({
  max: 5, // start blocking after 5 requests
  // message: 'Get out.', will be added
});

Use with RedisStore

ioredis options can be passed as config

config.type can be cluster / sentinel / standalone

const RateLimit = require('koa2-ratelimit-ioredis').RateLimit;
const Stores = require('koa2-ratelimit').Stores;

RateLimit.defaultOptions({
    message: 'Get out.',
    store: new Stores.Redis({
        host: 'redis_host',
        port: 'redis_port',
        password: 'redis_password',
        db: 1,
        type: 'standalone'
    })
});

const getUserLimiter = RateLimit.middleware({
    prefixKey: 'get/user/:id',
});
router.get('/user/:id', getUserLimiter, (ctx) => {});

const createAccountLimiter = RateLimit.middleware.middleware({
    prefixKey: 'post/user',
});
router.post('/user', createAccountLimiter, (ctx) => {});

// mount routes
app.use(router.middleware())

Use with SequelizeStore

const Sequelize = require('sequelize');
const RateLimit = require('koa2-ratelimit-ioredis').RateLimit;
const Stores = require('koa2-ratelimit-ioredis').Stores;

const sequelize = new Sequelize(/*your config to connected to bdd*/);

RateLimit.defaultOptions({
    message: 'Get out.',
    store: new Stores.Sequelize(sequelize, {
        tableName: 'ratelimits', // table to manage the middleware
        tableAbuseName: 'ratelimitsabuses', // table to store the history of abuses in.
    })
});

const getUserLimiter = RateLimit.middleware({
    prefixKey: 'get/user/:id',
});
router.get('/user/:id', getUserLimiter, (ctx) => {});

const createAccountLimiter = RateLimit.middleware.middleware({
    prefixKey: 'post/user',
});
router.post('/user', createAccountLimiter, (ctx) => {});

// mount routes
app.use(router.middleware())

Use with MongooseStore (Mongodb)

const mongoose = require('mongoose');
const RateLimit = require('koa2-ratelimit-ioredis').RateLimit;
const Stores = require('koa2-ratelimit-ioredis').Stores;

await mongoose.connect(/*your config to connected to bdd*/);

RateLimit.defaultOptions({
    message: 'Get out.',
    store: new Stores.Mongodb(mongoose.connection, {
        collectionName: 'ratelimits', // table to manage the middleware
        collectionAbuseName: 'ratelimitsabuses', // table to store the history of abuses in.
    }),
});

A ctx.state.rateLimit property is added to all requests with the limit, current, and remaining number of requests for usage in your application code.

Configuration

  • interval: Time Type - how long should records of requests be kept in memory. Defaults to 60000 (1 minute).

  • delayAfter: max number of connections during interval before starting to delay responses. Defaults to 1. Set to 0 to disable delaying.

  • timeWait: Time Type - how long to delay the response, multiplied by (number of recent hits - delayAfter). Defaults to 1000 (1 second). Set to 0 to disable delaying.

  • max: max number of connections during interval milliseconds before sending a 429 response code. Defaults to 5. Set to 0 to disable.

  • message: Error message returned when max is exceeded. Defaults to 'Too many requests, please try again later.'

  • statusCode: HTTP status code returned when max is exceeded. Defaults to 429.

  • headers: Enable headers for request limit (X-RateLimit-Limit) and current usage (X-RateLimit-Remaining) on all responses and time to wait before retrying (Retry-After) when max is exceeded.

  • skipFailedRequests: when true, failed requests (response status >= 400) won't be counted. Defaults to false.

  • whitelist: Array of whitelisted IPs to not be rate limited.

  • getUserId: Function used to get userId (if connected) to be added as key and saved in bdd, should an abuse case surface. Defaults:

    async function (ctx) {
        const whereFinds = [ctx.state.user, ctx.user, ctx.state.User, 
          ctx.User, ctx.state, ctx];
        const toFinds = ['id', 'userId', 'user_id', 'idUser', 'id_user'];
        for (const whereFind of whereFinds) {
          if (whereFind) {
            for (const toFind of toFinds) {
              if (whereFind[toFind]) {
                  return whereFind[toFind];
              }
            }
          }
        }
        return null;
    },
  • keyGenerator: Function used to generate keys. By default userID (if connected) or the user's IP address. Defaults:

    async function (ctx) {
        const userId = await this.options.getUserId(ctx);
        if (userId) {
            return `${this.options.prefixKey}|${userId}`;
        }
        return `${this.options.prefixKey}|${ctx.request.ip}`;
    }
  • skip: Function used to skip requests. Returning true from the function will skip limiting for that request. Defaults:

    async function (/*ctx*/) {
        return false;
    }
  • handler: The function to execute once the max limit has been exceeded. It receives the request and the response objects. The "next" param is available if you need to pass to the next middleware. Defaults:

    async function (ctx/*, next*/) {
        ctx.status = this.options.statusCode;
        ctx.body = { message: this.options.message };
        if (this.options.headers) {
            ctx.set('Retry-After', Math.ceil(this.options.interval / 1000));
        }
    }
  • onLimitReached: Function to listen each time the limit is reached. It call the store to save abuse, You can use it to debug/log. Defaults:

    async function (ctx) {
        this.store.saveAbuse({
            key: await this.options.keyGenerator(ctx),
            ip: ctx.request.ip,
            user_id: await this.options.getUserId(ctx),
        });
    }
  • weight: Function to set the incrementation of the counter depending on the request. Defaults:

    async function (/*ctx*/) {
        return 1;
    }
  • store: The storage to use when persisting rate limit attempts. By default, the MemoryStore is used.

    Avaliable data stores are:

    • MemoryStore: (default)Simple in-memory option. Does not share state when app has multiple processes or servers.
    • SequelizeStore: more suitable for large or demanding deployments.

The delayAfter and timeWait options were written for human-facing pages such as login and password reset forms. For public APIs, setting these to 0 (disabled) and relying on only interval and max for rate-limiting usually makes the most sense.

Time Type

Time type can be milliseconds or an object

    Times = {
        ms ?: number,
        sec ?: number,
        min ?: number,
        hour ?: number,
        day ?: number,
        week ?: number,
        month ?: number,
        year ?: number,
    };

Examples

    RateLimit.middleware({
        interval: { hour: 1, min: 30 }, // 1h30 window
        timeWait: { week: 2 }, // 2 weeks window
    });
    RateLimit.middleware({
        interval: { ms: 2000 }, // 2000 ms = 2 sec
        timeWait: 2000, // 2000 ms = 2 sec
    });

License

MIT © Rajan Panigrahi