diff --git a/backend/README.md b/backend/README.md index 2487046..1fdfe76 100644 --- a/backend/README.md +++ b/backend/README.md @@ -96,4 +96,4 @@ 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). -########################################################################### \ No newline at end of file +########################################################################### diff --git a/backend/package-lock.json b/backend/package-lock.json index 9d18e48..3addff7 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,6 +18,7 @@ "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.6", + "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.0", "@stellar/stellar-sdk": "^14.5.0", "@types/bcrypt": "^6.0.0", @@ -2599,6 +2600,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 35e4bdd..bac117b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -29,6 +29,7 @@ "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.6", + "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.0", "@stellar/stellar-sdk": "^14.5.0", "@types/bcrypt": "^6.0.0", diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index b63b81f..26d0f8c 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,5 +1,8 @@ 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'; import { User } from './users/entities/user.entity'; @@ -31,9 +34,35 @@ import { Player } from './games/entities/player.entity'; username: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'postgres', database: process.env.DB_NAME || 'myfans', - entities: [User, Post, Comment, Conversation, Message, Like, Game, Player], + entities: [ + User, + Post, + Comment, + Conversation, + Message, + Like, + Game, + Player, + ], 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, @@ -44,7 +73,13 @@ import { Player } from './games/entities/player.entity'; GamesModule, ], controllers: [AppController, ExampleController], - providers: [AppService], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, + ], }) export class AppModule { configure(consumer: MiddlewareConsumer) { 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();