add changes
This commit is contained in:
155
backend/middleware/security.js
Normal file
155
backend/middleware/security.js
Normal file
@@ -0,0 +1,155 @@
|
||||
const helmet = require('helmet');
|
||||
|
||||
/**
|
||||
* Helmet security configuration
|
||||
* Helmet helps secure Express apps by setting various HTTP headers
|
||||
*/
|
||||
const helmetConfig = helmet({
|
||||
// Content Security Policy
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"], // Allow inline styles for Swagger UI
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"], // Allow inline scripts for Swagger UI
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'", "data:"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"]
|
||||
}
|
||||
},
|
||||
|
||||
// Cross-Origin-Embedder-Policy
|
||||
crossOriginEmbedderPolicy: false, // Disabled for API compatibility
|
||||
|
||||
// Cross-Origin-Opener-Policy
|
||||
crossOriginOpenerPolicy: { policy: "same-origin" },
|
||||
|
||||
// Cross-Origin-Resource-Policy
|
||||
crossOriginResourcePolicy: { policy: "cross-origin" },
|
||||
|
||||
// DNS Prefetch Control
|
||||
dnsPrefetchControl: { allow: false },
|
||||
|
||||
// Expect-CT (deprecated but included for older browsers)
|
||||
expectCt: { maxAge: 86400 },
|
||||
|
||||
// Frameguard (prevent clickjacking)
|
||||
frameguard: { action: "deny" },
|
||||
|
||||
// Hide Powered-By header
|
||||
hidePoweredBy: true,
|
||||
|
||||
// HTTP Strict Transport Security
|
||||
hsts: {
|
||||
maxAge: 31536000, // 1 year
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
},
|
||||
|
||||
// IE No Open
|
||||
ieNoOpen: true,
|
||||
|
||||
// No Sniff (prevent MIME type sniffing)
|
||||
noSniff: true,
|
||||
|
||||
// Origin-Agent-Cluster
|
||||
originAgentCluster: true,
|
||||
|
||||
// Permitted Cross-Domain Policies
|
||||
permittedCrossDomainPolicies: { permittedPolicies: "none" },
|
||||
|
||||
// Referrer Policy
|
||||
referrerPolicy: { policy: "no-referrer" }
|
||||
});
|
||||
|
||||
/**
|
||||
* Custom security headers middleware
|
||||
* Only adds headers not already set by Helmet
|
||||
*/
|
||||
const customSecurityHeaders = (req, res, next) => {
|
||||
// Add Permissions-Policy (not in Helmet)
|
||||
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
||||
|
||||
// Prevent caching of sensitive data
|
||||
if (req.path.includes('/api/auth') || req.path.includes('/api/admin') || req.path.includes('/api/users')) {
|
||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
res.setHeader('Expires', '0');
|
||||
res.setHeader('Surrogate-Control', 'no-store');
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* CORS configuration
|
||||
*/
|
||||
const getCorsOptions = () => {
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS
|
||||
? process.env.ALLOWED_ORIGINS.split(',')
|
||||
: ['http://localhost:3000', 'http://localhost:4200', 'http://localhost:5173'];
|
||||
|
||||
return {
|
||||
origin: (origin, callback) => {
|
||||
// Allow requests with no origin (mobile apps, Postman, etc.)
|
||||
if (!origin) return callback(null, true);
|
||||
|
||||
if (allowedOrigins.indexOf(origin) !== -1 || process.env.NODE_ENV === 'development') {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'x-guest-token'],
|
||||
exposedHeaders: ['RateLimit-Limit', 'RateLimit-Remaining', 'RateLimit-Reset'],
|
||||
maxAge: 86400 // 24 hours
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Security middleware for API routes
|
||||
*/
|
||||
const secureApiRoutes = (req, res, next) => {
|
||||
// Log security-sensitive operations
|
||||
if (req.method !== 'GET' && req.path.includes('/api/admin')) {
|
||||
const logger = require('../config/logger');
|
||||
logger.logSecurityEvent(`Admin ${req.method} request`, req);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* Prevent parameter pollution
|
||||
* This middleware should be used after body parser
|
||||
*/
|
||||
const preventParameterPollution = (req, res, next) => {
|
||||
// Whitelist of parameters that can have multiple values
|
||||
const whitelist = ['category', 'difficulty', 'tags', 'keywords'];
|
||||
|
||||
// Check for duplicate parameters
|
||||
if (req.query) {
|
||||
for (const param in req.query) {
|
||||
if (Array.isArray(req.query[param]) && !whitelist.includes(param)) {
|
||||
return res.status(400).json({
|
||||
status: 'error',
|
||||
message: `Parameter pollution detected: '${param}' should not have multiple values`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
helmetConfig,
|
||||
customSecurityHeaders,
|
||||
getCorsOptions,
|
||||
secureApiRoutes,
|
||||
preventParameterPollution
|
||||
};
|
||||
Reference in New Issue
Block a user