security: add helmet, rate limiting, webhook auth, fix token storage, restrict hard deletes

- Add helmet for HTTP security headers (CSP, HSTS, X-Frame-Options, etc.)
- Add @nestjs/throttler for rate limiting (100 req/60s per IP)
- Add shared secret validation on Signal webhook endpoint
- Remove JWT token from localStorage, use Auth0 SDK memory cache
  with async getAccessTokenSilently() in API interceptor
- Restrict hard delete (?hard=true) to ADMINISTRATOR role in service layer
- Replace exposed Anthropic API key with placeholder in .env

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 18:30:14 +01:00
parent 8e88880838
commit 934464bf8e
18 changed files with 132 additions and 55 deletions

View File

@@ -13,6 +13,7 @@ import { VehiclesService } from './vehicles.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import { Role } from '@prisma/client';
import { CreateVehicleDto, UpdateVehicleDto } from './dto';
@@ -56,8 +57,12 @@ export class VehiclesController {
@Delete(':id')
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
remove(@Param('id') id: string, @Query('hard') hard?: string) {
remove(
@Param('id') id: string,
@Query('hard') hard?: string,
@CurrentUser() user?: any,
) {
const isHardDelete = hard === 'true';
return this.vehiclesService.remove(id, isHardDelete);
return this.vehiclesService.remove(id, isHardDelete, user?.role);
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable, NotFoundException, Logger } from '@nestjs/common';
import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateVehicleDto, UpdateVehicleDto } from './dto';
@@ -89,7 +89,11 @@ export class VehiclesService {
});
}
async remove(id: string, hardDelete = false) {
async remove(id: string, hardDelete = false, userRole?: string) {
if (hardDelete && userRole !== 'ADMINISTRATOR') {
throw new ForbiddenException('Only administrators can permanently delete records');
}
const vehicle = await this.findOne(id);
if (hardDelete) {