feat(security): production bootstrap hardening

This commit is contained in:
2026-03-29 19:26:59 +08:00
parent 6793460d31
commit 765d4507c9
2 changed files with 56 additions and 27 deletions

View File

@@ -49,6 +49,7 @@
"jsonwebtoken": "^9.0.2",
"passport": "^0.7.0",
"passport-discord": "^0.1.4",
"helmet": "^8.1.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"pg": "^8.16.3",

View File

@@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
import { ValidationPipe, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
import { RedisIoAdapter } from './ws/redis-io.adapter';
@@ -10,12 +11,28 @@ async function bootstrap() {
const logger = new Logger('Bootstrap');
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
const nodeEnv = configService.get<string>('NODE_ENV') || 'development';
const isProduction = nodeEnv === 'production';
app.enableShutdownHooks();
app.use(
helmet({
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
}),
);
// Configure Redis Adapter for horizontal scaling (if enabled)
const redisIoAdapter = new RedisIoAdapter(app, configService);
await redisIoAdapter.connectToRedis();
app.useWebSocketAdapter(redisIoAdapter);
app.enableCors({
origin: true,
credentials: true,
});
// Enable global exception filter for consistent error responses
app.useGlobalFilters(new AllExceptionsFilter());
@@ -29,11 +46,11 @@ async function bootstrap() {
// Automatically transform payloads to DTO instances
transform: true,
// Provide detailed error messages
disableErrorMessages: false,
disableErrorMessages: isProduction,
}),
);
// Configure Swagger documentation
if (!isProduction) {
const config = new DocumentBuilder()
.setTitle('Friendolls API')
.setDescription(
@@ -59,13 +76,24 @@ async function bootstrap() {
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
}
const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ?? 3000;
await app.listen(port);
const httpServer = app.getHttpServer() as {
once?: (event: 'close', listener: () => void) => void;
} | null;
httpServer?.once?.('close', () => {
void redisIoAdapter.close();
});
logger.log(`Application is running on: http://${host}:${port}`);
logger.log(`Swagger documentation available at: http://${host}:${port}/api`);
if (!isProduction) {
logger.log(
`Swagger documentation available at: http://${host}:${port}/api`,
);
}
}
void bootstrap();