Files
asle/backend/src/middleware/security.ts

120 lines
3.0 KiB
TypeScript
Raw Normal View History

import { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
/**
* Security middleware configurations
*/
// Rate limiting configurations
export const createRateLimiter = (windowMs: number, max: number) => {
return rateLimit({
windowMs,
max,
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
};
// Specific rate limiters
export const authRateLimiter = createRateLimiter(15 * 60 * 1000, 5); // 5 requests per 15 minutes
export const apiRateLimiter = createRateLimiter(60 * 1000, 100); // 100 requests per minute
export const strictRateLimiter = createRateLimiter(60 * 1000, 10); // 10 requests per minute
/**
* Enhanced security headers
*/
export const securityHeaders = helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: {
action: 'deny',
},
noSniff: true,
xssFilter: true,
});
/**
* CORS configuration based on environment
*/
export const corsConfig = {
origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => {
const allowedOrigins = process.env.CORS_ORIGINS?.split(',') || [process.env.CORS_ORIGIN || '*'];
if (process.env.NODE_ENV === 'production') {
if (!origin || allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
} else {
callback(null, true);
}
},
credentials: true,
optionsSuccessStatus: 200,
};
/**
* Input validation middleware
*/
export const validateInput = (schema: any) => {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse(req.body);
next();
} catch (error: any) {
res.status(400).json({
error: 'Validation error',
details: error.errors,
});
}
};
};
/**
* Sanitize input to prevent XSS
*/
export const sanitizeInput = (req: Request, res: Response, next: NextFunction) => {
if (req.body && typeof req.body === 'object') {
const sanitize = (obj: any): any => {
if (typeof obj === 'string') {
// Remove potentially dangerous characters
return obj.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
}
if (Array.isArray(obj)) {
return obj.map(sanitize);
}
if (obj && typeof obj === 'object') {
const sanitized: any = {};
for (const key in obj) {
sanitized[key] = sanitize(obj[key]);
}
return sanitized;
}
return obj;
};
req.body = sanitize(req.body);
}
next();
};