feat: implement security headers and rate limiting; update environment variables and documentation
This commit is contained in:
@@ -31,7 +31,8 @@
|
||||
"sharp": "^0.33.5",
|
||||
"tesseract.js": "^6.0.1",
|
||||
"uuid": "^11.1.0",
|
||||
"helmet": "^8.0.0"
|
||||
"helmet": "^8.0.0",
|
||||
"@nestjs/throttler": "^6.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.3.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
||||
import { HealthModule } from './health/health.module';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { ProductsModule } from './products/products.module';
|
||||
@@ -21,6 +22,13 @@ import { RolesGuard } from './auth/roles.guard';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ThrottlerModule.forRoot([
|
||||
{
|
||||
name: 'default',
|
||||
ttl: 60_000, // 1 minut
|
||||
limit: 120, // 120 anrop per minut (generellt)
|
||||
},
|
||||
]),
|
||||
HealthModule,
|
||||
PrismaModule,
|
||||
ProductsModule,
|
||||
@@ -38,6 +46,10 @@ import { RolesGuard } from './auth/roles.guard';
|
||||
AiModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: ThrottlerGuard,
|
||||
},
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: JwtAuthGuard,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
import { AuthService } from './auth.service';
|
||||
import { RegisterDto } from './dto/register.dto';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
@@ -9,12 +10,14 @@ export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Public()
|
||||
@Throttle({ default: { ttl: 60_000, limit: 10 } })
|
||||
@Post('register')
|
||||
register(@Body() dto: RegisterDto) {
|
||||
return this.authService.register(dto);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Throttle({ default: { ttl: 60_000, limit: 10 } })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('login')
|
||||
login(@Body() dto: LoginDto) {
|
||||
|
||||
@@ -11,7 +11,11 @@ import { UsersModule } from '../users/users.module';
|
||||
UsersModule,
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_SECRET ?? 'changeme',
|
||||
secret: (() => {
|
||||
const secret = process.env.JWT_SECRET;
|
||||
if (!secret) throw new Error('JWT_SECRET saknas i miljövariabler');
|
||||
return secret;
|
||||
})(),
|
||||
signOptions: { expiresIn: '7d' },
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -5,10 +5,12 @@ import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
const secret = process.env.JWT_SECRET;
|
||||
if (!secret) throw new Error('JWT_SECRET saknas i miljövariabler');
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: process.env.JWT_SECRET ?? 'changeme',
|
||||
secretOrKey: secret,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Query,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import { CreateProductDto } from './dto/create-product.dto';
|
||||
import { UpdateProductDto } from './dto/update-product.dto';
|
||||
@@ -89,6 +90,7 @@ export class ProductsController {
|
||||
|
||||
@Roles('admin')
|
||||
@Post('ai-categorize-bulk')
|
||||
@Throttle({ default: { ttl: 60_000, limit: 5 } })
|
||||
@HttpCode(200)
|
||||
async aiCategorizeBulk(@Body() body: AiCategorizeBulkDto) {
|
||||
const categories = await this.categoriesService.findFlattened();
|
||||
@@ -113,6 +115,7 @@ export class ProductsController {
|
||||
}
|
||||
|
||||
@Get(':id/suggest-category')
|
||||
@Throttle({ default: { ttl: 60_000, limit: 20 } })
|
||||
async suggestCategory(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: { user: { role: string; isPremium: boolean } },
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
UseInterceptors,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { memoryStorage } from 'multer';
|
||||
import { ReceiptImportService } from './receipt-import.service';
|
||||
@@ -27,6 +28,7 @@ export class ReceiptImportController {
|
||||
constructor(private readonly receiptImportService: ReceiptImportService) {}
|
||||
|
||||
@Post()
|
||||
@Throttle({ default: { ttl: 60_000, limit: 20 } })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(
|
||||
FileInterceptor('file', {
|
||||
|
||||
Reference in New Issue
Block a user