From 5e8767101c646b89562b893606bfa5cb5b090b0e Mon Sep 17 00:00:00 2001 From: Truphile Date: Sat, 27 Jun 2026 14:45:19 -0400 Subject: [PATCH] feat: extend rate limiting to all endpoints --- meridian-api/src/app.module.ts | 11 ++++++-- meridian-api/src/auth/auth.controller.ts | 8 +++--- .../common/guards/custom-throttler.guard.ts | 27 +++++++++++++++++++ meridian-api/src/health/health.controller.ts | 2 ++ 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 meridian-api/src/common/guards/custom-throttler.guard.ts diff --git a/meridian-api/src/app.module.ts b/meridian-api/src/app.module.ts index 2e5fdbd6..96f7c9b9 100644 --- a/meridian-api/src/app.module.ts +++ b/meridian-api/src/app.module.ts @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { APP_INTERCEPTOR, APP_GUARD } from '@nestjs/core'; +import { CustomThrottlerGuard } from './common/guards/custom-throttler.guard'; import { DataSource } from 'typeorm'; import { AppController } from './app.controller'; @@ -50,8 +51,14 @@ import { AuditModule } from './audit/audit.module'; */ ThrottlerModule.forRoot([ { + name: 'read', ttl: 60000, - limit: 10, + limit: 100, // 100 requests per minute for GET + }, + { + name: 'write', + ttl: 60000, + limit: 20, // 20 requests per minute for POST/PUT/PATCH/DELETE }, ]), @@ -117,7 +124,7 @@ import { AuditModule } from './audit/audit.module'; }, { provide: APP_GUARD, - useClass: ThrottlerGuard, + useClass: CustomThrottlerGuard, }, AccessTokenGuard, MailProvider, diff --git a/meridian-api/src/auth/auth.controller.ts b/meridian-api/src/auth/auth.controller.ts index 885a1335..cbb858f1 100644 --- a/meridian-api/src/auth/auth.controller.ts +++ b/meridian-api/src/auth/auth.controller.ts @@ -25,7 +25,7 @@ export class AuthController { constructor(private readonly authService: AuthService) {} @Post('/sign-in') - @Throttle({ default: { limit: 5, ttl: 15000 } }) + @Throttle({ write: { limit: 5, ttl: 15000 } }) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Sign in with user credentials' }) @ApiResponse({ @@ -42,7 +42,7 @@ export class AuthController { } @Post('/refresh-token') - @Throttle({ default: { limit: 10, ttl: 60000 } }) + @Throttle({ write: { limit: 10, ttl: 60000 } }) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Refresh Auth Token' }) @ApiResponse({ status: 200, description: 'Successfully refreshed token' }) @@ -97,7 +97,7 @@ export class AuthController { } @Post('/verify-email') - @Throttle({ default: { limit: 10, ttl: 60000 } }) + @Throttle({ write: { limit: 10, ttl: 60000 } }) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Verify email with one-time token from signup mail', @@ -113,7 +113,7 @@ export class AuthController { } @Post('/resend-verification') - @Throttle({ default: { limit: 3, ttl: 60000 } }) + @Throttle({ write: { limit: 3, ttl: 60000 } }) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Resend the email verification mail' }) @ApiResponse({ diff --git a/meridian-api/src/common/guards/custom-throttler.guard.ts b/meridian-api/src/common/guards/custom-throttler.guard.ts new file mode 100644 index 00000000..b817c972 --- /dev/null +++ b/meridian-api/src/common/guards/custom-throttler.guard.ts @@ -0,0 +1,27 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { ThrottlerGuard } from '@nestjs/throttler'; + +@Injectable() +export class CustomThrottlerGuard extends ThrottlerGuard { + protected async handleRequest( + requestProps: any, + ): Promise { + const { context, throttler } = requestProps; + + const req = context.switchToHttp().getRequest(); + const isWrite = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method); + const throttlerName = throttler.name || 'default'; + + // If it's a read request, ignore the "write" throttler + if (!isWrite && throttlerName === 'write') { + return true; + } + + // If it's a write request, ignore the "read" throttler + if (isWrite && throttlerName === 'read') { + return true; + } + + return super.handleRequest(requestProps); + } +} diff --git a/meridian-api/src/health/health.controller.ts b/meridian-api/src/health/health.controller.ts index 505495cd..941db40d 100644 --- a/meridian-api/src/health/health.controller.ts +++ b/meridian-api/src/health/health.controller.ts @@ -1,4 +1,5 @@ import { Controller, Get } from '@nestjs/common'; +import { SkipThrottle } from '@nestjs/throttler'; import { HealthCheckService, HealthCheck, @@ -8,6 +9,7 @@ import { } from '@nestjs/terminus'; import { ApiTags, ApiOperation } from '@nestjs/swagger'; +@SkipThrottle() @ApiTags('Health') @Controller('health') export class HealthController {