From 3ba5cff98b859793213ec2e1c82b46a4ce1cec0a Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:25:53 +0100 Subject: [PATCH] feat: rate limits --- apps/server/package.json | 2 + apps/server/src/app.module.ts | 2 + apps/server/src/core/auth/auth.controller.ts | 5 +++ apps/server/src/ee | 2 +- .../integrations/throttle/throttle.module.ts | 34 +++++++++++++++++ pnpm-lock.yaml | 37 +++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/integrations/throttle/throttle.module.ts diff --git a/apps/server/package.json b/apps/server/package.json index d8bab08c..d1704ce6 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -44,6 +44,7 @@ "@langchain/core": "1.1.34", "@langchain/textsplitters": "1.0.1", "@modelcontextprotocol/sdk": "^1.27.1", + "@nest-lab/throttler-storage-redis": "^1.2.0", "@nestjs-labs/nestjs-ioredis": "^11.0.4", "@nestjs/bullmq": "^11.0.4", "@nestjs/cache-manager": "^3.1.0", @@ -58,6 +59,7 @@ "@nestjs/platform-socket.io": "^11.1.17", "@nestjs/schedule": "^6.1.1", "@nestjs/terminus": "^11.1.1", + "@nestjs/throttler": "^6.5.0", "@nestjs/websockets": "^11.1.17", "@node-saml/passport-saml": "^5.1.0", "@react-email/components": "1.0.10", diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 6280ee09..b8cfc587 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -26,6 +26,7 @@ import KeyvRedis from '@keyv/redis'; import { LoggerModule } from './common/logger/logger.module'; import { ClsModule } from 'nestjs-cls'; import { NoopAuditModule } from './integrations/audit/audit.module'; +import { ThrottleModule } from './integrations/throttle/throttle.module'; const enterpriseModules = []; try { @@ -83,6 +84,7 @@ try { EventEmitterModule.forRoot(), SecurityModule, TelemetryModule, + ThrottleModule, ...enterpriseModules, ], controllers: [AppController], diff --git a/apps/server/src/core/auth/auth.controller.ts b/apps/server/src/core/auth/auth.controller.ts index 6eab6539..441bfc1c 100644 --- a/apps/server/src/core/auth/auth.controller.ts +++ b/apps/server/src/core/auth/auth.controller.ts @@ -10,6 +10,7 @@ import { UseGuards, Logger, } from '@nestjs/common'; +import { SkipThrottle, ThrottlerGuard } from '@nestjs/throttler'; import { LoginDto } from './dto/login.dto'; import { AuthService } from './services/auth.service'; import { SessionService } from '../session/session.service'; @@ -33,6 +34,7 @@ import { IAuditService, } from '../../integrations/audit/audit.service'; +@UseGuards(ThrottlerGuard) @Controller('auth') export class AuthController { private readonly logger = new Logger(AuthController.name); @@ -111,6 +113,7 @@ export class AuthController { return workspace; } + @SkipThrottle() @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @Post('change-password') @@ -173,6 +176,7 @@ export class AuthController { return this.authService.verifyUserToken(verifyUserTokenDto, workspace.id); } + @SkipThrottle() @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @Post('collab-token') @@ -183,6 +187,7 @@ export class AuthController { return this.authService.getCollabToken(user, workspace.id); } + @SkipThrottle() @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @Post('logout') diff --git a/apps/server/src/ee b/apps/server/src/ee index 05f1c816..350ef574 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 05f1c816a839072efc1143cce71322a9ed6b4a0a +Subproject commit 350ef574e398c318aa57ce5f79ab12e9d8329dcb diff --git a/apps/server/src/integrations/throttle/throttle.module.ts b/apps/server/src/integrations/throttle/throttle.module.ts new file mode 100644 index 00000000..7dab4a16 --- /dev/null +++ b/apps/server/src/integrations/throttle/throttle.module.ts @@ -0,0 +1,34 @@ +import { Module } from '@nestjs/common'; +import { ThrottlerModule } from '@nestjs/throttler'; +import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'; +import { EnvironmentService } from '../environment/environment.service'; +import { EnvironmentModule } from '../environment/environment.module'; +import { parseRedisUrl } from '../../common/helpers'; +import Redis from 'ioredis'; + +@Module({ + imports: [ + ThrottlerModule.forRootAsync({ + imports: [EnvironmentModule], + useFactory: (environmentService: EnvironmentService) => { + const redisConfig = parseRedisUrl(environmentService.getRedisUrl()); + + return { + throttlers: [{ name: 'auth', ttl: 60_000, limit: 10 }], + storage: new ThrottlerStorageRedisService( + new Redis({ + host: redisConfig.host, + port: redisConfig.port, + password: redisConfig.password, + db: redisConfig.db, + family: redisConfig.family, + keyPrefix: 'throttle:', + }), + ), + }; + }, + inject: [EnvironmentService], + }), + ], +}) +export class ThrottleModule {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0389d23..a8273db3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -493,6 +493,9 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.27.1 version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@nest-lab/throttler-storage-redis': + specifier: ^1.2.0 + version: 1.2.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2))(ioredis@5.10.1)(reflect-metadata@0.2.2) '@nestjs-labs/nestjs-ioredis': specifier: ^11.0.4 version: 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(ioredis@5.10.1) @@ -535,6 +538,9 @@ importers: '@nestjs/terminus': specifier: ^11.1.1 version: 11.1.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/throttler': + specifier: ^6.5.0 + version: 6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2) '@nestjs/websockets': specifier: ^11.1.17 version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/platform-socket.io@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -2925,6 +2931,15 @@ packages: '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@nest-lab/throttler-storage-redis@1.2.0': + resolution: {integrity: sha512-tMkUyo68NCKTR+zILk+EC35SMYBtDPZY2mCj7ZaCietWGVTnuP4zwq9ERYfvU6kJv6h8teNZrC6MJCmY6/dljw==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/throttler': '>=6.0.0' + ioredis: '>=5.0.0' + reflect-metadata: ^0.2.1 + '@nestjs-labs/nestjs-ioredis@11.0.4': resolution: {integrity: sha512-4jPNOrxDiwNMIN5OLmsMWhA782kxv/ZBxkySX9l8n6sr55acHX/BciaFsOXVa/ILsm+Y7893y98/6WNhmEoiNQ==} engines: {node: '>=16'} @@ -3127,6 +3142,13 @@ packages: '@nestjs/platform-express': optional: true + '@nestjs/throttler@6.5.0': + resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + '@nestjs/websockets@11.1.17': resolution: {integrity: sha512-YbwQ0QfVj0lxkKQhdIIgk14ZSVWDqGk1J8nNSN6SLjf36sVv58Ma5ro+dtQua8wj3l2Ub7JJCVFixEhKtYc/rQ==} peerDependencies: @@ -13463,6 +13485,15 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@nest-lab/throttler-storage-redis@1.2.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2))(ioredis@5.10.1)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/throttler': 6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2) + ioredis: 5.10.1 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + '@nestjs-labs/nestjs-ioredis@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(ioredis@5.10.1)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -13643,6 +13674,12 @@ snapshots: '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 + '@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + '@nestjs/websockets@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/platform-socket.io@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)