feat(security): production bootstrap hardening
This commit is contained in:
@@ -49,6 +49,7 @@
|
|||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-discord": "^0.1.4",
|
"passport-discord": "^0.1.4",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
|
|||||||
82
src/main.ts
82
src/main.ts
@@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
|||||||
import { ValidationPipe, Logger } from '@nestjs/common';
|
import { ValidationPipe, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import helmet from 'helmet';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
|
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
|
||||||
import { RedisIoAdapter } from './ws/redis-io.adapter';
|
import { RedisIoAdapter } from './ws/redis-io.adapter';
|
||||||
@@ -10,12 +11,28 @@ async function bootstrap() {
|
|||||||
const logger = new Logger('Bootstrap');
|
const logger = new Logger('Bootstrap');
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
const configService = app.get(ConfigService);
|
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)
|
// Configure Redis Adapter for horizontal scaling (if enabled)
|
||||||
const redisIoAdapter = new RedisIoAdapter(app, configService);
|
const redisIoAdapter = new RedisIoAdapter(app, configService);
|
||||||
await redisIoAdapter.connectToRedis();
|
await redisIoAdapter.connectToRedis();
|
||||||
app.useWebSocketAdapter(redisIoAdapter);
|
app.useWebSocketAdapter(redisIoAdapter);
|
||||||
|
|
||||||
|
app.enableCors({
|
||||||
|
origin: true,
|
||||||
|
credentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Enable global exception filter for consistent error responses
|
// Enable global exception filter for consistent error responses
|
||||||
app.useGlobalFilters(new AllExceptionsFilter());
|
app.useGlobalFilters(new AllExceptionsFilter());
|
||||||
|
|
||||||
@@ -29,43 +46,54 @@ async function bootstrap() {
|
|||||||
// Automatically transform payloads to DTO instances
|
// Automatically transform payloads to DTO instances
|
||||||
transform: true,
|
transform: true,
|
||||||
// Provide detailed error messages
|
// Provide detailed error messages
|
||||||
disableErrorMessages: false,
|
disableErrorMessages: isProduction,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Configure Swagger documentation
|
if (!isProduction) {
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Friendolls API')
|
.setTitle('Friendolls API')
|
||||||
.setDescription(
|
.setDescription(
|
||||||
'API for managing users in Friendolls application.\n\n' +
|
'API for managing users in Friendolls application.\n\n' +
|
||||||
'Authentication is handled via Passport.js social sign-in for desktop clients.\n' +
|
'Authentication is handled via Passport.js social sign-in for desktop clients.\n' +
|
||||||
'Desktop clients exchange one-time SSO codes for Friendolls JWT tokens.\n\n' +
|
'Desktop clients exchange one-time SSO codes for Friendolls JWT tokens.\n\n' +
|
||||||
'Include the JWT token in the Authorization header as: `Bearer <token>`',
|
'Include the JWT token in the Authorization header as: `Bearer <token>`',
|
||||||
)
|
)
|
||||||
.setVersion('1.0')
|
.setVersion('1.0')
|
||||||
.addBearerAuth(
|
.addBearerAuth(
|
||||||
{
|
{
|
||||||
type: 'http',
|
type: 'http',
|
||||||
scheme: 'bearer',
|
scheme: 'bearer',
|
||||||
bearerFormat: 'JWT',
|
bearerFormat: 'JWT',
|
||||||
name: 'Authorization',
|
name: 'Authorization',
|
||||||
description: 'Enter Friendolls JWT access token',
|
description: 'Enter Friendolls JWT access token',
|
||||||
in: 'header',
|
in: 'header',
|
||||||
},
|
},
|
||||||
'bearer',
|
'bearer',
|
||||||
)
|
)
|
||||||
.addTag('users', 'User profile management endpoints')
|
.addTag('users', 'User profile management endpoints')
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('api', app, document);
|
SwaggerModule.setup('api', app, document);
|
||||||
|
}
|
||||||
|
|
||||||
const host = process.env.HOST ?? 'localhost';
|
const host = process.env.HOST ?? 'localhost';
|
||||||
const port = process.env.PORT ?? 3000;
|
const port = process.env.PORT ?? 3000;
|
||||||
await app.listen(port);
|
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(`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();
|
void bootstrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user