diff --git a/backend/package.json b/backend/package.json index 7e1ef68..4bfcd9e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -33,6 +33,7 @@ "@nestjs/passport": "^9.0.0", "@nestjs/platform-fastify": "^9.2.1", "@nestjs/serve-static": "^3.0.0", + "@nestjs/throttler": "^3.1.0", "@nestjs/typeorm": "^9.0.1", "bcrypt": "^5.1.0", "bmp-img": "^1.2.1", diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index be513ef..b439a66 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -5,6 +5,7 @@ import { IncomingMessage, ServerResponse } from 'http'; import { EarlyConfigModule } from './config/early/early-config.module'; import { ServeStaticConfigService } from './config/early/serve-static.config.service'; import { DatabaseModule } from './database/database.module'; +import { PicsurLayersModule } from './layers/PicsurLayers.module'; import { PicsurLoggerModule } from './logger/logger.module'; import { AuthManagerModule } from './managers/auth/auth.module'; import { DemoManagerModule } from './managers/demo/demo.module'; @@ -45,6 +46,7 @@ const imageCorsOverride = ( AuthManagerModule, DemoManagerModule, PicsurRoutesModule, + PicsurLayersModule, ], }) export class AppModule implements NestModule { diff --git a/backend/src/layers/PicsurLayers.module.ts b/backend/src/layers/PicsurLayers.module.ts new file mode 100644 index 0000000..121f309 --- /dev/null +++ b/backend/src/layers/PicsurLayers.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { ThrottlerModule } from '@nestjs/throttler'; +import { MainExceptionFilter } from './exception/exception.filter'; +import { SuccessInterceptor } from './success/success.interceptor'; +import { PicsurThrottlerGuard } from './throttler/PicsurThrottler.guard'; +import { ZodValidationPipe } from './validate/zod-validator.pipe'; + +@Module({ + imports: [ThrottlerModule.forRoot()], + providers: [ + PicsurThrottlerGuard, + MainExceptionFilter, + SuccessInterceptor, + ZodValidationPipe, + ], + exports: [ + PicsurThrottlerGuard, + MainExceptionFilter, + SuccessInterceptor, + ZodValidationPipe, + ], +}) +export class PicsurLayersModule {} diff --git a/backend/src/layers/throttler/PicsurThrottler.guard.ts b/backend/src/layers/throttler/PicsurThrottler.guard.ts new file mode 100644 index 0000000..3f1bb0f --- /dev/null +++ b/backend/src/layers/throttler/PicsurThrottler.guard.ts @@ -0,0 +1,10 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { ThrottlerGuard } from '@nestjs/throttler'; +import { Fail, FT } from 'picsur-shared/dist/types'; + +@Injectable() +export class PicsurThrottlerGuard extends ThrottlerGuard { + protected override throwThrottlingException(context: ExecutionContext): void { + throw Fail(FT.Permission, undefined, 'Too many requests'); + } +} diff --git a/backend/src/main.ts b/backend/src/main.ts index db756ad..9f0dcb8 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -11,6 +11,7 @@ import { UserDbService } from './collections/user-db/user-db.service'; import { HostConfigService } from './config/early/host.config.service'; import { MainExceptionFilter } from './layers/exception/exception.filter'; import { SuccessInterceptor } from './layers/success/success.interceptor'; +import { PicsurThrottlerGuard } from './layers/throttler/PicsurThrottler.guard'; import { ZodValidationPipe } from './layers/validate/zod-validator.pipe'; import { PicsurLoggerService } from './logger/logger.service'; import { MainAuthGuard } from './managers/auth/guards/main.guard'; @@ -20,7 +21,14 @@ async function bootstrap() { const isProduction = process.env['PICSUR_PRODUCTION'] !== undefined; // Create fasify - const fastifyAdapter = new FastifyAdapter(); + const fastifyAdapter = new FastifyAdapter({ + trustProxy: [ + '127.0.0.0/8', + '10.0.0.0/8', + '172.16.0.0/12', + '192.168.0.0/16', + ], + }); // TODO: generic error messages await fastifyAdapter.register(multipart as any); await fastifyAdapter.register(fastifyHelmet as any, HelmetOptions); @@ -40,12 +48,11 @@ async function bootstrap() { app.flushLogs(); - app.useGlobalFilters(new MainExceptionFilter()); - app.useGlobalInterceptors(new SuccessInterceptor(app.get(Reflector))); - app.useGlobalPipes(new ZodValidationPipe()); - app.useGlobalGuards( - new MainAuthGuard(app.get(Reflector), app.get(UserDbService)), - ); + app.useGlobalFilters(app.get(MainExceptionFilter)); + app.useGlobalInterceptors(app.get(SuccessInterceptor)); + app.useGlobalPipes(app.get(ZodValidationPipe)); + + app.useGlobalGuards(app.get(PicsurThrottlerGuard), app.get(MainAuthGuard)); // Start app const hostConfigService = app.get(HostConfigService); diff --git a/backend/src/managers/auth/auth.module.ts b/backend/src/managers/auth/auth.module.ts index 7343a1e..2eca869 100644 --- a/backend/src/managers/auth/auth.module.ts +++ b/backend/src/managers/auth/auth.module.ts @@ -13,7 +13,9 @@ import { AuthManagerService } from './auth.service'; import { ApiKeyStrategy } from './guards/apikey.strategy'; import { GuestStrategy } from './guards/guest.strategy'; import { JwtStrategy } from './guards/jwt.strategy'; +import { LocalAuthGuard } from './guards/local-auth.guard'; import { LocalAuthStrategy } from './guards/local-auth.strategy'; +import { MainAuthGuard } from './guards/main.guard'; import { GuestService } from './guest.service'; @Module({ @@ -30,13 +32,15 @@ import { GuestService } from './guest.service'; ], providers: [ AuthManagerService, + GuestService, + JwtSecretProvider, LocalAuthStrategy, JwtStrategy, GuestStrategy, - JwtSecretProvider, ApiKeyStrategy, - GuestService, + LocalAuthGuard, + MainAuthGuard, ], - exports: [UserDbModule, AuthManagerService], + exports: [UserDbModule, AuthManagerService, LocalAuthGuard, MainAuthGuard], }) export class AuthManagerModule {} diff --git a/yarn.lock b/yarn.lock index 4840024..540a34c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3108,6 +3108,19 @@ __metadata: languageName: node linkType: hard +"@nestjs/throttler@npm:^3.1.0": + version: 3.1.0 + resolution: "@nestjs/throttler@npm:3.1.0" + dependencies: + md5: ^2.2.1 + peerDependencies: + "@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0 + "@nestjs/core": ^7.0.0 || ^8.0.0 || ^9.0.0 + reflect-metadata: ^0.1.13 + checksum: fc0776d709aa5160005a2a86f6315cca9ce2d1c8dc8bf562810030f30e38e03b23ab4b7e265bdb70eb209dc84498a1390d1ba666eb3487f250dce308f31524b4 + languageName: node + linkType: hard + "@nestjs/typeorm@npm:^9.0.1": version: 9.0.1 resolution: "@nestjs/typeorm@npm:9.0.1" @@ -4807,6 +4820,13 @@ __metadata: languageName: node linkType: hard +"charenc@npm:0.0.2": + version: 0.0.2 + resolution: "charenc@npm:0.0.2" + checksum: 81dcadbe57e861d527faf6dd3855dc857395a1c4d6781f4847288ab23cffb7b3ee80d57c15bba7252ffe3e5e8019db767757ee7975663ad2ca0939bb8fcaf2e5 + languageName: node + linkType: hard + "chokidar@npm:3.5.3, chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.0.0, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -5222,6 +5242,13 @@ __metadata: languageName: node linkType: hard +"crypt@npm:0.0.2": + version: 0.0.2 + resolution: "crypt@npm:0.0.2" + checksum: baf4c7bbe05df656ec230018af8cf7dbe8c14b36b98726939cef008d473f6fe7a4fad906cfea4062c93af516f1550a3f43ceb4d6615329612c6511378ed9fe34 + languageName: node + linkType: hard + "css-loader@npm:6.7.3": version: 6.7.3 resolution: "css-loader@npm:6.7.3" @@ -7304,6 +7331,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:~1.1.6": + version: 1.1.6 + resolution: "is-buffer@npm:1.1.6" + checksum: 4a186d995d8bbf9153b4bd9ff9fd04ae75068fe695d29025d25e592d9488911eeece84eefbd8fa41b8ddcc0711058a71d4c466dcf6f1f6e1d83830052d8ca707 + languageName: node + linkType: hard + "is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0": version: 2.9.0 resolution: "is-core-module@npm:2.9.0" @@ -8035,6 +8069,17 @@ __metadata: languageName: node linkType: hard +"md5@npm:^2.2.1": + version: 2.3.0 + resolution: "md5@npm:2.3.0" + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: ~1.1.6 + checksum: a63cacf4018dc9dee08c36e6f924a64ced735b37826116c905717c41cebeb41a522f7a526ba6ad578f9c80f02cb365033ccd67fe186ffbcc1a1faeb75daa9b6e + languageName: node + linkType: hard + "media-typer@npm:0.3.0": version: 0.3.0 resolution: "media-typer@npm:0.3.0" @@ -9346,6 +9391,7 @@ __metadata: "@nestjs/schematics": ^9.0.3 "@nestjs/serve-static": ^3.0.0 "@nestjs/testing": ^9.2.1 + "@nestjs/throttler": ^3.1.0 "@nestjs/typeorm": ^9.0.1 "@types/bcrypt": ^5.0.0 "@types/cors": ^2.8.13