131 lines
4.0 KiB
JavaScript
131 lines
4.0 KiB
JavaScript
const express = require('express');
|
|
const helmet = require('helmet');
|
|
const cors = require('cors');
|
|
const compression = require('compression');
|
|
const mongoSanitize = require('express-mongo-sanitize');
|
|
const hpp = require('hpp');
|
|
const { errorHandler } = require('./middleware/errorHandler');
|
|
const { requestLogger } = require('./middleware/requestLogger');
|
|
const { authMiddleware, roleCheck } = require('./middleware/auth');
|
|
const { validateRequest } = require('./middleware/validation');
|
|
const { correlationIdMiddleware } = require('./middleware/correlationId');
|
|
const { metricsMiddleware } = require('./middleware/metrics');
|
|
const { rateLimiterRedis } = require('./utils/rateLimiter');
|
|
const { cache } = require('./utils/cache');
|
|
const routes = require('./routes');
|
|
const swaggerUi = require('swagger-ui-express');
|
|
const swaggerDocument = require('./swagger.json');
|
|
const logger = require('./utils/logger');
|
|
const { AppError } = require('./utils/errors');
|
|
|
|
const app = express();
|
|
|
|
app.use(helmet({
|
|
contentSecurityPolicy: {
|
|
useDefaults: true,
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
imgSrc: ["'self'", 'data:', 'https:'],
|
|
connectSrc: ["'self'"],
|
|
frameSrc: ["'none'"],
|
|
objectSrc: ["'none'"]
|
|
}
|
|
},
|
|
crossOriginEmbedderPolicy: true,
|
|
crossOriginOpenerPolicy: true,
|
|
crossOriginResourcePolicy: { policy: 'same-origin' },
|
|
dnsPrefetchControl: { allow: false },
|
|
frameguard: { action: 'deny' },
|
|
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
|
|
ieNoOpen: true,
|
|
noSniff: true,
|
|
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
|
xssFilter: true,
|
|
permittedCrossDomainPolicies: { permittedPolicies: 'none' }
|
|
}));
|
|
|
|
app.use(cors({
|
|
origin: async (origin, callback) => {
|
|
try {
|
|
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
|
|
if (!origin || allowedOrigins.includes(origin)) {
|
|
callback(null, true);
|
|
} else {
|
|
throw new AppError('Not allowed by CORS', 403);
|
|
}
|
|
} catch (error) {
|
|
callback(error);
|
|
}
|
|
},
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-Correlation-ID'],
|
|
credentials: true,
|
|
maxAge: parseInt(process.env.CORS_MAX_AGE) || 86400
|
|
}));
|
|
|
|
app.use(compression());
|
|
app.use(express.json({ limit: '10kb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
|
|
app.use(mongoSanitize());
|
|
app.use(hpp());
|
|
app.use(correlationIdMiddleware);
|
|
app.use(metricsMiddleware);
|
|
app.use(rateLimiterRedis);
|
|
app.use(requestLogger);
|
|
app.use(cache);
|
|
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
const healthData = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime(),
|
|
memoryUsage: process.memoryUsage(),
|
|
version: process.env.npm_package_version
|
|
};
|
|
res.status(200).json(healthData);
|
|
} catch (error) {
|
|
logger.error('Health check failed:', { error: error.message, stack: error.stack });
|
|
res.status(503).json({ status: 'error', message: 'Service unavailable' });
|
|
}
|
|
});
|
|
|
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
|
|
explorer: true,
|
|
customCss: '.swagger-ui .topbar { display: none }',
|
|
swaggerOptions: {
|
|
persistAuthorization: true,
|
|
docExpansion: 'none',
|
|
filter: true
|
|
}
|
|
}));
|
|
|
|
app.use('/api', authMiddleware, validateRequest, roleCheck, routes);
|
|
|
|
app.use('*', (req, res) => {
|
|
res.status(404).json({
|
|
status: 'error',
|
|
message: 'Resource not found',
|
|
path: req.originalUrl
|
|
});
|
|
});
|
|
|
|
app.use(errorHandler);
|
|
|
|
process.on('unhandledRejection', (err) => {
|
|
logger.error('Unhandled Rejection:', { error: err.message, stack: err.stack });
|
|
if (process.env.NODE_ENV === 'production') {
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
process.on('uncaughtException', (err) => {
|
|
logger.error('Uncaught Exception:', { error: err.message, stack: err.stack });
|
|
if (process.env.NODE_ENV === 'production') {
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
module.exports = app; |