fix(security): harden auth, upload, and API configuration
- Lock public registration to first-user bootstrap (403 afterwards) - Require JwtAuthGuard on upload + whitelist png/jpg/webp/gif (SVG/XSS blocked) - Add global throttling (login/register 5/min, upload 10/min) - Add helmet security headers; serve uploads with nosniff - Replace permissive CORS (origin:true+credentials) with CORS_ORIGINS whitelist - Disable Swagger outside development; sanitize 500 error responses - Enforce 32+ char JWT_SECRET; make token expiry configurable (TOKEN_EXPIRES_IN) - Re-check user in DB on every JWT validation (revocation on user delete) - Dummy bcrypt compare to prevent login user-enumeration via timing - Map malformed BigInt inputs to 400 instead of 500 - Widen .gitignore to .env* and add apps/api/.env.example - Disable Nuxt devtools and sourcemaps
This commit is contained in:
@@ -32,9 +32,24 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
const status =
|
||||
exception instanceof HttpException
|
||||
? exception.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
// Malformed bigint/number inputs (e.g. `BigInt("abc")`) are client
|
||||
// errors — map them to 400 instead of leaking a 500.
|
||||
if (
|
||||
status === HttpStatus.INTERNAL_SERVER_ERROR &&
|
||||
exception instanceof Error &&
|
||||
/Cannot convert .+ to (a BigInt|number)/i.test(exception.message)
|
||||
) {
|
||||
response.status(HttpStatus.BAD_REQUEST).json({
|
||||
statusCode: HttpStatus.BAD_REQUEST,
|
||||
message: 'Invalid numeric identifier',
|
||||
error: 'BadRequestError',
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let message: string | string[] = 'Internal server error';
|
||||
let error = 'InternalServerError';
|
||||
@@ -51,11 +66,15 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
||||
message = exception.message;
|
||||
}
|
||||
} else if (exception instanceof Error) {
|
||||
message = exception.message;
|
||||
error = exception.name;
|
||||
// Unexpected errors (Prisma, driver, ...) may contain SQL or
|
||||
// connection details — never send them to the client.
|
||||
this.logger.error(
|
||||
`${request.method} ${request.url} -> ${status} ${exception.message}`,
|
||||
exception.stack,
|
||||
);
|
||||
}
|
||||
|
||||
if (status >= 500) {
|
||||
if (status >= 500 && exception instanceof HttpException) {
|
||||
this.logger.error(
|
||||
`${request.method} ${request.url} -> ${status} ${message}`,
|
||||
exception instanceof Error ? exception.stack : undefined,
|
||||
|
||||
Reference in New Issue
Block a user