Code Monkey home page Code Monkey logo

nestjs-ratelimiter's Introduction

nest-ratelimiter

npm npm GitHub branch checks state Known Vulnerabilities Libraries.io Dependabot Supported platforms: Express & Fastify

The most flexible NestJS rate limiter based on Redis (rate limit against not only req path but req body to block distributed brute force attacks).

Install

npm i nest-ratelimiter ratelimiter @types/ratelimiter

If you want to use the default response when reaching a limit (text: "Rate limit exceeded, retry in human readable time value") also install ms.

npm i nest-ratelimiter ratelimiter @types/ratelimiter ms

Usage

Decorator

Let's start with controllers. Controllers are the places where you set parameters for the rate-limiter guard. You can set parameters for an entire controller or handler. Also, you can override the parameters of an entire controller by providing parameters for a specific handler. And finally, you can set several parameters for multi-checking.

import { RateLimiter, LimiterInfo } from 'nest-ratelimiter';

// Let's define several functions that returns the identifier
// to limit against.

// This is functions for limiting requests by IP
function getRequestIP(ctx: ExecutionContext) {
  const request = ctx.switchToHttp().getRequest();
  return request.ip;
}

// Also you can limit every path separately
function getRequestIPAndPath(ctx: ExecutionContext) {
  const request = ctx.switchToHttp().getRequest();
  return `${request.ip}:${request.path}`;
}

// For blocking brute force attacks on login
// you can return `login` value as identifier
function getRequestBodyLogin(ctx: ExecutionContext) {
  const request = ctx.switchToHttp().getRequest();
  return request.body.login;
}

// Now let's setup controller

@Controller('/')
// set params for entire controller
@RateLimiter({ getId: getRequestIP })
class TestController {
  // without providing params for specific handler
  // it will inherit params of entire controller
  @Get('some-api')
  someApi() {
    // ...
  }

  @Get('some-other-api')
  // override params for specific handler
  @RateLimiter({
    getId: getRequestIPAndPath,
    max: 10,
    duration: 10000,
  })
  someOtherApi() {
    // ...
  }

  @Get('one-more-api')
  // turn off rate limiter for specific handler
  @RateLimiter(false)
  oneMoreApi() {
    // ...
  }

  @Get('login')
  // override params for specific handler
  // by providing several params
  @RateLimiter(
    {
      getId: getRequestIPAndPath,
      max: 3,
      duration: 60 * 60 * 1000,
    },
    {
      getId: getRequestBodyLogin,
      max: 3,
      duration: 60 * 60 * 1000,
      // this is default `createErrorBody` function
      // but you can set your own
      createErrorBody: (limit: LimiterInfo) => {
        const delta = limit.reset * 1000 - Date.now();
        // ms is imported from `ms` module
        const readable = ms(delta, { long: true });
        return 'Rate limit exceeded, retry in ' + readable;
      },
    },
  )
  login(creds: CredsDto) {
    // ...
  }
}

Please, check out the docs of ratelimiter npm module for a better understanding of @RateLimiter configuration.

Service

Another feature is using rate limiting in complex scenarios when id could not be retrieved from request context. For example when it's required to make a request for id in 3rd party systems:

import {
  RATE_LIMITER_ASSERTER_TOKEN,
  RateLimiterAsserter,
  setHeaders,
} from 'nest-ratelimiter'

@Controller('/')
class TestController {
  constructor(
    @Inject(RATE_LIMITER_ASSERTER_TOKEN)
    private asserter: RateLimiterAsserter,
    private db: DB;
  ) {}

  @Get('some-api')
  someApi(
    @Res({ passthrough: true }) response: any
  ) {
    const id = await this.db.getId();

    // this potentially throws `RateLimiterError` which is handled by internal
    // filter and mapped to `TooManyRequestsException`. If that doesn't fit your
    // needs, semply use filters, interceptors, try/catch to handle those errors
    const limiterInfo = this.asserter.assert({
      id,
      max: 10,
      duration: 24 * 60 * 60 * 1000,
    });

    // In this simple example limiterInfo is retrieved in controller and
    // `X-RateLimit-...` headers could be easily set with `setHeaders` function.
    // In a real world scenario this is done on a services layer and in a such
    // case limiterInfo should be passed back to a controller where there is an
    // access to underlying framework's response object. But this is optional
    // and only required if there is a need for such headers in a positive case.
    setHeaders(response, limiterInfo);
  }
}

Setup

Let's move to module registration. As nest-ratelimiter is using Redis as a data storage you have to provide an instance of Redis client (redis or ioredis). As Redis client instantiation is out of the scope of this package, you can find something that fits your needs on npm or create your own module for NestJS. Here we will show two examples: with redis and nestjs-redis modules:

import { RedisModule } from 'nestjs-redis';
import { RateLimiterModule, LimiterInfo } from 'nest-ratelimiter';

@Module({
  imports: [

    // redis example

    RateLimiterModule.forRoot({

      // The only required field is `db` (redis client), all the rest fields
      // will be used as defaults for `@RateLimiter(...)` and RateLimiterAsserter
      db: require("redis").createClient()

    }),

    // nestjs-redis example

    RateLimiterModule.forRootAsync({

      // 1 Register third-party module that provides `redis` or `ioredis` client
      imports: [
        RedisModule.register({
          host: process.env.REDIS_HOST,
          port: parseInt(process.env.REDIS_PORT),
          db: parseInt(process.env.REDIS_DB),
        }),
      ],

      // 2 And then inject redis client provider
      inject: [RedisService],

      // 3. build and return `RateLimiterModuleParams` from factory
      useFactory: async (redisService: RedisService) => {

        // You can set default fields for every @RateLimiter and then you don't
        // have to copy-paste your params on entire codebase.

        // IF YOU SET `getId` HERE, THEN ALL CONTROLLERS (EVEN THOSE WITHOUT
        // @RateLimiter GUARD) WILL USE THIS FUNCTION BY DEFAULT. IF IN THAT
        // CASE YOU NEED TO TURN OFF RATE LIMITER ON SOME SPECIFIC HANDLER OR
        // CONTROLLER JUST USE `@RateLimiter(false)`

        return {
          db: redisService.getClient(),
          max: 10,
          duration: 10000,
          getId: getRequestIPAndPath;
          createErrorBody: (limit: LimiterInfo) => ({
            error: {
              code: 'MY-RATE-LIMIT-ERROR-CODE',
              params: limit,
            },
          }),
        };
      },

    }),
  ],
  controllers: [TestController],
})
class TestModule {}

Comparison with others

This nest-ratelimiter is using TJ's ratelimiter package underhood, so it allows the creation of a flexible strategy for limiting not only per request path but per headers or body values or even asynchronously computed values on a services layer. It stores data only in redis. If you need another store you can look at nestjs-rate-limiter, but it allows the use of strategies based on a request path only. Also, there is an example in official docs with setting up express-rate-limit middleware.

Migration

0.3.0

  • no need to use app.useGlobalGuards as now it's set automatically
  • dropped support of nestjs < 8.0.0
  • dropped support of node < 16.0.0

0.2.0

  • nestjs-redis was moved from dependencies, now you are free to use any redis module that fit your needs, but you have to set new field RateLimiterModuleParams.db that should be redis or ioredis instance.
  • ratelimiter (with @types/ratelimiter) was moved to peer dependencies. If you are using npm@7 it will install it automatically, either way you should install it manually.

Do you use this library?
Don't be shy to give it a star! โ˜…

Also if you are into NestJS you might be interested in one of my other NestJS libs.

nestjs-ratelimiter's People

Contributors

actions-user avatar alinavavilova avatar dependabot-preview[bot] avatar dependabot[bot] avatar github-actions[bot] avatar iamolegga avatar mergify[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

nestjs-ratelimiter's Issues

[QUESTION] Contact

Question

Hi @iamolegga! I'd love to get in touch with you but I'm not sure what's the best way to reach out. Let me know!

Feel free to close & remove this issue

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.