diff --git a/app/backend/src/app.module.ts b/app/backend/src/app.module.ts index 7d6ad5c..2a4edde 100644 --- a/app/backend/src/app.module.ts +++ b/app/backend/src/app.module.ts @@ -31,6 +31,7 @@ import { LoggingInterceptor } from './interceptors/logging.interceptor'; import { LoggerService } from './logger/logger.service'; import { AllExceptionsFilter } from './common/filters/http-exception.filter'; import { AnalyticsModule } from './analytics/analytics.module'; +import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { AidEscrowModule } from './onchain/aid-escrow.module'; @Module({ @@ -75,7 +76,14 @@ import { AidEscrowModule } from './onchain/aid-escrow.module'; JobsModule, AnalyticsModule, AidEscrowModule, + ThrottlerModule.forRoot([ + { + ttl: 60000, // 60 seconds window + limit: 20, // default: 20 req/min + }, + ]), ], + controllers: [AppController], providers: [ @@ -96,6 +104,10 @@ import { AidEscrowModule } from './onchain/aid-escrow.module'; provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, }, + { + provide: APP_GUARD, + useClass: ThrottlerGuard, // rate-limiting guard runs after auth and role checks to avoid unnecessary counting of unauthenticated/unauthorized requests + }, ], }) export class AppModule implements NestModule { diff --git a/app/backend/src/campaigns/campaigns.controller.ts b/app/backend/src/campaigns/campaigns.controller.ts index 26fab88..e65e4d5 100644 --- a/app/backend/src/campaigns/campaigns.controller.ts +++ b/app/backend/src/campaigns/campaigns.controller.ts @@ -30,6 +30,7 @@ import { UpdateCampaignDto } from './dto/update-campaign.dto'; import { ApiResponseDto } from '../common/dto/api-response.dto'; import { Roles } from 'src/auth/roles.decorator'; import { AppRole } from 'src/auth/app-role.enum'; +import { Throttle } from '@nestjs/throttler'; import { OrgOwnershipGuard } from '../common/guards/org-ownership.guard'; @ApiTags('Campaigns') @@ -57,7 +58,8 @@ export class CampaignsController { const campaign = await this.campaigns.create(dto, req.user?.ngoId); return ApiResponseDto.ok(campaign, 'Campaigns created successfully'); } - + + @Throttle({ default: { ttl: 60000, limit: 10 } }) // Limit to 10 requests per minute for this endpoint @Get() @ApiOperation({ summary: 'List all campaigns', diff --git a/app/backend/src/health/health.controller.ts b/app/backend/src/health/health.controller.ts index 5ed3a83..ce63803 100644 --- a/app/backend/src/health/health.controller.ts +++ b/app/backend/src/health/health.controller.ts @@ -12,6 +12,7 @@ import { HealthService } from './health.service'; import { LivenessResponse, ReadinessResponse } from './health.service'; import { API_VERSIONS } from '../common/constants/api-version.constants'; import { Public } from '../common/decorators/public.decorator'; +import { Throttle } from '@nestjs/throttler'; @ApiTags('Health') @Controller('health') @@ -20,6 +21,7 @@ export class HealthController { @Public() @Get() + @Throttle({ default: { ttl: 60, limit: 100 } }) // Limit to 100 requests per minute for this endpoint @Version(API_VERSIONS.V1) @ApiOperation({ summary: 'Check system liveness and basic service metadata',