Coder Social home page Coder Social logo

kkoomen / nestjs-throttler-storage-redis Goto Github PK

View Code? Open in Web Editor NEW
142.0 6.0 16.0 7.65 MB

Redis storage provider for the nestjs-throttler package.

License: MIT License

JavaScript 5.86% TypeScript 93.24% Shell 0.90%
nestjs throttler redis storage rate-limit express fastify websocket socketio graphql

nestjs-throttler-storage-redis's Introduction

Hi there 👋

My name is Kim. Learning Chinese since August 2017. Software engineer since 2013.

Since 2022 back at university studying Artificial Intelligence, not because of the hype, but just because it's one of the few things I haven't learned yet that I would really like to learn.

On the road to a PhD:

  • 📕 Webdevelopment (4y)
  • 📗 Software Engineering foundation year (1y)
  • 📘 Artificial Intelligence BSc (3y)
  • 📙 Pattern Recognition and Intelligent Systems MSc (2.5y)
  • 🎓 PhD

kkoomen GitHub stats

Top Langs

nestjs-throttler-storage-redis's People

Contributors

davide-gheri avatar dependabot-preview[bot] avatar dependabot[bot] avatar kkoomen avatar mickaelmesdocteurs avatar moisout avatar nkovacic avatar ssut avatar zizitto 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  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  avatar  avatar

nestjs-throttler-storage-redis's Issues

Request flooding bypasses rate limit to some extend

The service doesn't work properly when there are for example more than 5 requests in a second, for example, the limitation is 5 requests per second.

if we did the requests by Postman or Browser all will work fine.

But if we use the script to make requests for example 1000 per second, the guard will not work and will forward a request to the controller. @Throttle(5, 1)

I changed storage from Redis to memory and it works fine for 1000 requests for a second, and after 5 requests the guard is activating.

As I understand the logic written in functions addRecord and getRecord works very slowly, for that it's working when we make requests by the postman and doesn't work when we use the script for checking.

connecting with forRootAsync doesn't work

Hello,
I have been trying to connect redis storage to nest throttler. But connection with forRootAsync keeps failing.


It works in case of forRoot.

ThrottlerModule.forRoot({
  throttlers: [
    {
      ttl: 60,
      limit: 10,
    },
  ],
  storage: new ThrottlerStorageRedisService(new Redis()),
})

But it is not in forRootAsync using useFactory.

ThrottlerModule.forRootAsync({
  inject: [ConfigService],
  useFactory: async (config: ConfigService) => [
    {
      ttl: config.get<ThrottleConfig>('throttle').ttl,
      limit: config.get<ThrottleConfig>('throttle').limit,
      storage: new ThrottlerStorageRedisService(new Redis()),
    },
  ],
})

I've checked on redis-cli monitor, and even tried this but failed again.

ThrottlerModule.forRootAsync({
  inject: [ConfigService],
  useFactory: async (config: ConfigService) => {
    const redisClient = new Redis();

    const test = () => {
      return new Promise((resolve, reject) => {
        redisClient.on('connect', () => {
          resolve(true);
        });
        redisClient.on('error', () => {
          reject(false);
        });
      });
    };

    await test();

    return [
      {
        ttl: config.get<ThrottleConfig>('throttle').ttl,
        limit: config.get<ThrottleConfig>('throttle').limit,
        storage: new ThrottlerStorageRedisService(
          redisClient,
        ),
      },
    ];

packages:

"ioredis": "^5.3.2",
"nestjs-throttler-storage-redis": "^0.4.1",
"@nestjs/throttler": "^5.0.1"

Has anyone got this problem?

nest 8 compatibility

thanks for your work!

it looks like this lib is compatible with nest 8
can you please change peerDependencies to read something like

"@nestjs/common": "^7 || ^8",

Issue Fixed in 322 Still Exists

The issue in #322 still exists, but in a different form. Here is what I discovered:

There is a pretty big bug here, since it uses the main db0 on your redis instance if you have any other data other than this in that DB it won't scan past the SCANCOUNT. So it simply doesn't work if your Redis DB is full of tons of data. You may want to rewrite this so it works differently.

Not working for limit greater than 10_000

I think, we should at least add a warning to Readme, that for a limit greater than 10 thousand this will not work because of this redis scan query: https://github.com/kkoomen/nestjs-throttler-storage-redis/blob/master/src/throttler-storage-redis.service.ts#L23 .

I believe that a limit smaller than 10 000 is fine for most cases. I have an API service where the free tier has 15 000 requests in a month (30 days), so I get stuck for a while why is this not working in production. Locally I tested it with a smaller limit and everything worked fine.

You can close this if you think that nobody else will hit this problem.

That is a bad idea to use Redis SCAN command, it works not like you expects...

Example:
My Redis Instance is also used as cache storage there are 260k keys right now. Cached keys have a big TTL, so they are always there.
Your "addRecord" method always writes a new key to the end of the keys list. (With much lower TTL)

Your "getRecord" method uses a SCAN command with "COUNT" property.
By default "COUNT" property is set to 1000 (by you).

Probably you expect that COUNT property is used to limit the number of rows returned by SCAN command...
But actually, COUNT property limits the number of keys to check with MATCH pattern, by default it will scan only the first 1000 keys stored in Redis.
So it never gets to the rows written by "addRecord" method in my case.

Extending of count property value to something like "300000" is not an option, because it will impact SCAN performance.

I would highly recommend you to replace the SCAN command with something else.

Also, I would highly recommend avoiding the usage of this package until this issue is fixed.
In my case, the throttler was not working on the production environment for a loooong time, until I found this issue.

connect aws ElastiCache with seperate read and write

In the basic set up I have code like below, which has only one redis node:

import { Module } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { ThrottlerModule } from '@nestjs/throttler'
import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis'

@Module({
  imports: [
    ThrottlerModule.forRootAsync({
      useFactory: (cfg: ConfigService) => ({
        ttl: 60,
        limit: 600,
        storage: new ThrottlerStorageRedisService({
          host: cfg.get('REDIS_HOST'),
          port: 6379
        })
      }),
      inject: [ConfigService]
    })
  ]
})
export class RateLimitingModule {}

After add another new node to aws ElastiCache, there are Primary endpoint and Reader endpoint:

Primary Endpoint: my-project.abcde.ng.0001.euc1.cache.amazonaws.com:6379
Reader Endpoint: my-project-ro.abcde.ng.0002.euc1.cache.amazonaws.com:6379

How can I set up the connection with redis so writes will arrive at Primary endpoint and reads will be Reader endpoint?

Redis storage does is not working - no errors

Rate limiter does not seem to work when using Redis. However, no errors are being thrown to indicate that the connection is unsuccessful.

   ThrottlerModule.forRoot({
      ttl: 60,
      limit: 100,
      storage: new ThrottlerStorageRedisService(
        new Redis({
          port: Number(process.env.REDIS_PORT),
          host: process.env.REDIS_HOST,
        }),
      ),
    }),

Packages:

 "ioredis": "^5.0.2",
"nestjs-throttler-storage-redis": "^0.3.0",
"redis": "^4.0.6",
 "@types/ioredis": "^4.28.10",

Just seeing if anyone has any ideas?

fix - redis keyPrefix option is not supported

configuring a keyPrefix in RedisOptions doesnt seem to be supported

configure whatever you want as the keyPrefix:

ThrottlerModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        throttlers: [
          {
            ttl: config.get('THROTTLE_TTL'),
            limit: config.get('THROTTLE_LIMIT'),
          },
        ],
        storage: new ThrottlerStorageRedisService(
        {
        host: config.get('REDIS_HOST'),
        port: config.get('REDIS_PORT'),
        keyPrefix: 'my-very-special-prefix',
      }),
      }),
    }),

the redis key will still remain the same

unable to implement custom guard

hi there! thanks for the awesome lib

with the last update, I'm unable to re-implement my custom guard

import { Injectable, ExecutionContext } from "@nestjs/common";
import { ThrottlerGuard } from "@nestjs/throttler";
import { WsException } from "@nestjs/websockets";

@Injectable()
export class ThrottlerWsGuard extends ThrottlerGuard {
  async handleRequest(context: ExecutionContext, limit: number, ttl: number): Promise<boolean> {
    const client = context.switchToWs().getClient();
    const ip = ["conn", "_socket"]
      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
      .map(key => client[key])
      .filter(obj => obj)
      .shift().remoteAddress;
    const key = this.generateKey(context, ip);
    const ttls = await this.storageService.getRecord(key);

    if (ttls.length >= limit) {
      this.throwThrottlingException();
    }

    await this.storageService.addRecord(key, ttl);
    return true;
  }

  protected throwThrottlingException(): void {
    throw new WsException("tooManyRequests");
  }
}

because addRecord was renamed, that is ok, but getRecord was removed and I need your advice of what is an alternative

WRONGTYPE Operation against a key holding the wrong kind of value

Hey, just upgraded to version 4.0.0 two days ago, and since then getting sometimes this error:

ERR Error running script (call to f_f7788d43191d9ad66c866e8c63f3c3e361391849): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value
stack:ReplyError: ERR Error running script (call to f_f7788d43191d9ad66c866e8c63f3c3e361391849): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value
at parseError (/usr/src/app/node_modules/redis-parser/lib/parser.js:179:12)
at parseType (/usr/src/app/node_modules/redis-parser/lib/parser.js:302:14)
name:ReplyError
type:ReplyError
}

Happens on the increment operation...Has anyone encountered this??

Wrong `timeToExpire` unit

timeToExpire value returned from the increment function is in milliseconds instead of seconds. Because of that NestJs ThrottlerGuard sets the wrong Retry-After header in the HTTP response when the throttling limit is hit.

From the quick debug session, it looks like calling the PTTL Redis command returns milliseconds that are not converted to seconds afterwards.

if pass redis options will raise error

  imports: [
    ThrottlerModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config) => ({ storage: new ThrottlerStorageRedisService(config.get('redis[1]')) }), // if pass options
    }),
  ],
{host:'127.0.0.1',port:6379,name:'1',db:1}

UnhandledPromiseRejectionWarning: ReplyError: ERR value is not an integer or out of range

version

  • npm ioredis ^4.19.4
  • server redis 6

update

always throw error if requested endpoint is protected

Support redis cluster

When ThrottlerStorageRedisService get an instance of Redis.Cluster, the instance meets the last else, then it tries connect default host(127.0.0.0) and port(6379)

constructor(redisOrOptions?: Redis.Redis | Redis.RedisOptions | string, scanCount?: number) {
this.scanCount = typeof scanCount === 'undefined' ? 1000 : scanCount;
if (redisOrOptions instanceof Redis) {
this.redis = redisOrOptions;
} else if (typeof redisOrOptions === 'string') {
this.redis = new Redis(redisOrOptions as string);
this.disconnectRequired = true;
} else {
this.redis = new Redis(redisOrOptions);

serious bug: remaining times is stuck when use "storage" option with nestjs-redis

I'm submitting a...


[ ] Regression 
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request

Current behavior

export const Throttler: DynamicModule = ThrottlerModule.forRootAsync({
  imports: [Redis],
  inject: [RedisService],
  useFactory(redisService: RedisService): ThrottlerModuleOptions {
    const redisClient = redisService.getClient(REDIS_CLIENT_NAME_THROTTLER);

    return {
      ttl: ms('1m') / 1000,
      limit: 30,
      storage: new ThrottlerStorageRedisService(redisClient),
    };
  },
});
  • The X-RateLimit-Remaining in headers of response is stuck when send request about 11-12 times.
  • But if not use "storage" option, it works normally.
  • I created a mini repo below for this issue.

Expected behavior

The throttler should works normally.

Minimal reproduction of the problem with instructions

  1. download from https://github.com/DevAngsy/throttle-bug
  2. npm install
  3. edit redis password according to your redis config
  4. npm run start:dev
  5. send get request to http://127.0.0.1:3000/app/hello 10-20 times, and notice X-RateLimit-Remaining
  6. single-line annotate "storage" option, notice X-RateLimit-Remaining again

Environment

OS Version: Ubuntu 20.04.2 LTS
CPU: Intel® Core™ i5-9600K
NodeJS Version: 14.16.0
NPM Version: 7.6.3
Ubuntu Redis Version: V6
Global Nest CLI Version: 7.5.6
Repo Nest CLI Version: 7.5.6
Repo nestjs-throttler Version: 0.3.0
Repo nestjs-throttler-storage-redis Version: 0.1.11
Repo ioredis Version: 4.24.3

`Maximum call stack size exceeded` error while e2e test my NestJS app

Thanks for this awesome module that make my life easier

The initial question:

Is it normal to face Maximum call stack size exceeded error while running e2e test on AppModule?

Description:

My NestJS application runs in the prod mode and dev mode. But I cannot execute an e2e test on the AppModule. Basically I have too many 3rd party packages and it happens while importing ThrottlerStorageRedisService and other 3rd party modules are OK.

My assumption:

I am not sure it was something related to Jest or NestJS. Just my guess something between your package and ioredis is wrong. As you see in the screenshot the call stack error happened in that module.

Why I guess like that?

I test this solution too, I import it as the last module, All other modules imported correctly but this module cause Maximum call stack size exceeded error.

Expected behavior

My e2e runs and I could write my e2e tests.

What happened:

This error that you see occurred.
Screenshot from it

Node version: 17.6.0
And here is my devDep and dep copied from package.json:

"scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:e2e": "NODE_ENV=test jest --config ./test/e2e.config.ts --watch",
},
"dependencies": {
    "@nestjs-modules/mailer": "^1.6.1",
    "@nestjs/bull": "^0.5.0",
    "@nestjs/common": "^8.0.0",
    "@nestjs/config": "^1.1.6",
    "@nestjs/core": "^8.0.0",
    "@nestjs/event-emitter": "^1.0.0",
    "@nestjs/jwt": "^8.0.0",
    "@nestjs/passport": "^8.0.1",
    "@nestjs/platform-express": "^8.0.0",
    "@nestjs/schedule": "^1.0.2",
    "@nestjs/swagger": "^5.1.4",
    "@nestjs/throttler": "^2.0.0",
    "buffer": "^6.0.3",
    "bull": "^4.6.2",
    "cache-manager": "^3.6.0",
    "cache-manager-redis-store": "^2.0.0",
    "env-cmd": "^10.1.0",
    "ioredis": "^4.28.5",
    "nestjs-throttler-storage-redis": "^0.1.18",
    "reflect-metadata": "^0.1.13",
},
"devDependencies": {
    "@nestjs/cli": "^8.0.0",
    "@nestjs/schematics": "^8.0.0",
    "@nestjs/testing": "^8.0.0",
    "@types/bcrypt": "^5.0.0",
    "@types/bcryptjs": "^2.4.2",
    "@types/bull": "^3.15.8",
    "@types/cache-manager": "^3.4.2",
    "@types/cron": "^1.7.3",
    "@types/express": "^4.17.13",
    "@types/jest": "^27.0.1",
    "@types/node": "^16.0.0",
    "@types/passport-jwt": "^3.0.6",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^4.28.2",
    "@typescript-eslint/parser": "^4.28.2",
    "eslint": "^7.30.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^3.4.0",
    "jest": "^27.0.6",
    "prettier": "^2.3.2",
    "prisma": "^3.3.0",
    "prisma-dbml-generator": "^0.8.3",
    "supertest": "^6.1.3",
    "ts-jest": "^27.0.3",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "^3.10.1",
    "typescript": "^4.3.5"
},

Do you have any Idea what is wrong?

session storage

Hello there!

Can I use this storage as storage for session middleware instead of https://github.com/tj/connect-redis ?
I just don't see a reason to have whole bunch of redis connectors like I have now and want to use only one for everything

Problems with Redis storage

I have one issue that can understand:
When I use ThrottlerStorageRedisService into ThrottlerModule.forRoot and pass redis as prameter Redis don't keeps track of the requests.
I wanna see all request information into Redis memory but its empty.
Maybe I should use getRecord and addRecord methods from ThrottlerStorageRedisService directly into my endpoints?

New release with updated peer dependencies

Hi

With NPM 7 the resolution of peer dependencies is much more strict. As such it would be nice to get a release done of this, so the updated peer dependencies requirements are out.

Thanks.

version issue

"reflect-metadata": "^0.2.2",
"cache-manager": "^5.5.1",
"cache-manager-ioredis-yet": "^2.0.3",
"nestjs-throttler-storage-redis": "^0.4.4",
"@nestjs/cache-manager": "^2.2.2",

"ioredis": "^5.4.0" or "^5.3.2"

code

import { Injectable, Logger, LoggerService } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import {
    ThrottlerModuleOptions,
    ThrottlerOptions,
    ThrottlerOptionsFactory
} from '@nestjs/throttler'

import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis'
import { RedisOptions } from 'ioredis'

import { ConfigEnum } from './config'

@Injectable()
export class ThrottlerConfig implements ThrottlerOptionsFactory {
    private readonly logger: LoggerService = new Logger(ThrottlerConfig.name)

    constructor(private readonly configService: ConfigService) {}

    createThrottlerOptions(): ThrottlerModuleOptions {
        const throttlers = this.configService.get<ThrottlerOptions>(
            ConfigEnum.THROTTLER
        ) ?? {
            ttl:
                this.configService.get<number>(ConfigEnum.THROTTLE_TTL) ??
                600000,
            limit:
                this.configService.get<number>(ConfigEnum.THROTTLE_LIMIT) ?? 20
        }

        const redisOptions = this.configService.get<RedisOptions>(
            ConfigEnum.REDIS_URL
        )

        this.logger.log('ThrottlerConfig.createThrottlerOptions', redisOptions)

        return {
            throttlers: [throttlers],
       -> -> ->    storage: new ThrottlerStorageRedisService(redisOptions)
        }
    }
}

model:

ThrottlerModule.forRootAsync({
            imports: [ConfigModule],
            useClass: ThrottlerConfig
        }),

errror:

[Nest] 16928  - 04/17/2024, 2:18:26 PM   ERROR [ExceptionsHandler] ERR Error running script (call to f_f7788d43191d9ad66c866e8c63f3c3e361391849): @user_script:5: @user_script: 5: Lua redis() command arguments must be strings or integers
ReplyError: ERR Error running script (call to f_f7788d43191d9ad66c866e8c63f3c3e361391849): @user_script:5: @user_script: 5: Lua redis() command arguments must be strings or integers
   at parseError (C:\Users\weeam\one-cp\one-cp-proxy\node_modules\redis-parser\lib\parser.js:179:12)
   at parseType (C:\Users\weeam\one-cp\one-cp-proxy\node_modules\redis-parser\lib\parser.js:302:14)

disable the line storage: new ThrottlerStorageRedisService(redisOptions) code work fine

Update peer dependencies on @nestjs/common and @nestjs/throttler to latest version compatible versions

Having a Nestjs at version 9, if installing this package it results in an peer dependency mismatch error.

❯ npm install --save nestjs-throttler-storage-redis
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: [email protected]
npm ERR! Found: @nestjs/[email protected]
npm ERR! node_modules/@nestjs/common
npm ERR!   @nestjs/common@"^9.0.1" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer @nestjs/common@"^7 || ^8" from [email protected]
npm ERR! node_modules/nestjs-throttler-storage-redis
npm ERR!   nestjs-throttler-storage-redis@"*" from the root project
ERESOLVE overriding peer dependency
npm WARN While resolving: [email protected]
npm WARN Found: @nestjs/[email protected]
npm WARN node_modules/@nestjs/throttler
npm WARN   @nestjs/throttler@"^3.0.0" from the root project
npm WARN 
npm WARN Could not resolve dependency:
npm WARN peer @nestjs/throttler@"^2.0.0" from [email protected]
npm WARN node_modules/nestjs-throttler-storage-redis
npm WARN   nestjs-throttler-storage-redis@"*" from the root project

Dynamically update config values of limiter

Hi,
Is it possible with current code architecture to dynamically update config values of this service? The idea is not to redeploy server in case i want to change limit and duration values of limiter.
Thanks

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.