From 23f4d1b47886175f8e7c1d8b42468566e9a1ada2 Mon Sep 17 00:00:00 2001 From: Henry Peters Date: Sat, 21 Feb 2026 05:39:43 +0100 Subject: [PATCH 1/2] feat: add rate limiting --- backend/README.md | 56 +++++++++++++++++++++++++++++ backend/package-lock.json | 12 +++++++ backend/package.json | 1 + backend/src/app.module.ts | 23 +++++++++++- backend/src/auth/auth.controller.ts | 11 ++++++ backend/src/auth/throttler.guard.ts | 17 +++++++++ backend/src/main.ts | 4 +++ 7 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 backend/src/auth/throttler.guard.ts diff --git a/backend/README.md b/backend/README.md index 8f0f65f..afb5f32 100644 --- a/backend/README.md +++ b/backend/README.md @@ -96,3 +96,59 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors ## License Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). + +## Rate Limiting + +The MyFans backend implements rate limiting to protect against abuse and ensure fair usage. Rate limiting is implemented using `@nestjs/throttler`. + +### Rate Limit Configuration + +| Route Type | Limit | Time Window | Description | +|------------|-------|-------------|-------------| +| Global (all routes) | 100 requests | 60 seconds | Default limit per IP address | +| Auth routes (login, register) | 5 requests | 60 seconds | Stricter limit to prevent brute-force attacks | +| Health check | Unlimited | - | Excluded from rate limiting | + +### HTTP Status Codes + +- **200 OK**: Request successful +- **429 Too Many Requests**: Rate limit exceeded + +### Rate Limit Headers + +When a rate limit is exceeded, the API returns a `429` status with the following headers: + +- `Retry-After`: Seconds until the rate limit resets + +### Example Error Response + +```json +{ + "statusCode": 429, + "message": "Too many requests", + "error": "Rate limit exceeded. Please try again later." +} +``` + +### Configuration + +Rate limiting can be configured in `src/app.module.ts`: + +```typescript +ThrottlerModule.forRoot([ + { + name: 'short', + ttl: 60000, // 60 seconds in milliseconds + limit: 100, // max requests per TTL + }, + { + name: 'auth', + ttl: 60000, + limit: 5, + }, +]), +``` + +### Redis Support (Optional) + +For distributed rate limiting across multiple instances, you can configure Redis storage. Install `ioredis` and configure the throttler to use Redis as the storage backend. diff --git a/backend/package-lock.json b/backend/package-lock.json index ce713db..75b1752 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "@nestjs/core": "^11.0.1", "@nestjs/mapped-types": "^2.1.0", "@nestjs/platform-express": "^11.0.1", + "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", @@ -2537,6 +2538,17 @@ } } }, + "node_modules/@nestjs/throttler": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.5.0.tgz", + "integrity": "sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==", + "license": "MIT", + "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" + } + }, "node_modules/@nestjs/typeorm": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index da198c1..1d0fa11 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,6 +24,7 @@ "@nestjs/core": "^11.0.1", "@nestjs/mapped-types": "^2.1.0", "@nestjs/platform-express": "^11.0.1", + "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index de979d2..113e836 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ThrottlerModule } from '@nestjs/throttler'; +import { APP_GUARD } from '@nestjs/core'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { User } from './users/entities/user.entity'; +import { ThrottlerGuard } from './auth/throttler.guard'; @Module({ imports: [ @@ -16,8 +19,26 @@ import { User } from './users/entities/user.entity'; entities: [User], synchronize: true, }), + ThrottlerModule.forRoot([ + { + name: 'short', + ttl: 60000, + limit: 100, + }, + { + name: 'auth', + ttl: 60000, + limit: 5, + }, + ]), ], controllers: [AppController], - providers: [AppService], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, + ], }) export class AppModule {} diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts index 7d3d1ac..cc837c9 100644 --- a/backend/src/auth/auth.controller.ts +++ b/backend/src/auth/auth.controller.ts @@ -1,4 +1,5 @@ import { Controller, Post, Body } from '@nestjs/common'; +import { Throttle } from '@nestjs/throttler'; import { AuthService } from './auth.service'; @Controller('auth') @@ -6,10 +7,20 @@ export class AuthController { constructor(private authService: AuthService) {} @Post('login') + @Throttle({ auth: { limit: 5, ttl: 60000 } }) async login(@Body() body: { address: string }) { if (!this.authService.validateStellarAddress(body.address)) { return { error: 'Invalid Stellar address' }; } return this.authService.createSession(body.address); } + + @Post('register') + @Throttle({ auth: { limit: 5, ttl: 60000 } }) + async register(@Body() body: { address: string }) { + if (!this.authService.validateStellarAddress(body.address)) { + return { error: 'Invalid Stellar address' }; + } + return this.authService.createSession(body.address); + } } diff --git a/backend/src/auth/throttler.guard.ts b/backend/src/auth/throttler.guard.ts new file mode 100644 index 0000000..3dd196f --- /dev/null +++ b/backend/src/auth/throttler.guard.ts @@ -0,0 +1,17 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { ThrottlerGuard as NestThrottlerGuard } from '@nestjs/throttler'; + +@Injectable() +export class ThrottlerGuard extends NestThrottlerGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest<{ url?: string }>(); + const url = request.url ?? ''; + + // Exclude health check routes from rate limiting + if (url === '/health' || url.startsWith('/health/')) { + return true; + } + + return super.canActivate(context); + } +} diff --git a/backend/src/main.ts b/backend/src/main.ts index f951358..41968b7 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -11,6 +11,10 @@ async function bootstrap() { transform: true, }), ); + + // Enable CORS + app.enableCors(); + await app.listen(process.env.PORT ?? 3000); } bootstrap(); From 9468d4598764de8f2a3797ce92a46b2785babb6d Mon Sep 17 00:00:00 2001 From: Henry Peters Date: Mon, 23 Feb 2026 17:20:01 +0100 Subject: [PATCH 2/2] feat: fix app.module.ts --- backend/src/app.module.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index c789b45..03508c7 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,6 +1,7 @@ import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ThrottlerModule } from '@nestjs/throttler'; +import { ThrottlerGuard } from './auth/throttler.guard'; import { APP_GUARD } from '@nestjs/core'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @@ -32,6 +33,23 @@ import { Like } from './likes/entities/like.entity'; entities: [User, Post, Comment, Conversation, Message, Like], synchronize: true, }), + ThrottlerModule.forRoot([ + { + name: 'short', + ttl: 1000, + limit: 10, + }, + { + name: 'medium', + ttl: 10000, + limit: 50, + }, + { + name: 'long', + ttl: 60000, + limit: 200, + }, + ]), HealthModule, LoggingModule, CreatorsModule,