Files
telegram-management-system/marketing-agent/services/api-gateway/src/middleware/security.js
你的用户名 237c7802e5
Some checks failed
Deploy / deploy (push) Has been cancelled
Initial commit: Telegram Management System
Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 15:37:50 +08:00

304 lines
8.0 KiB
JavaScript

import helmet from 'helmet';
import cors from 'cors';
import hpp from 'hpp';
import mongoSanitize from 'express-mongo-sanitize';
import { logger } from '../utils/logger.js';
import { config } from '../config/index.js';
import crypto from 'crypto';
/**
* Configure CORS
*/
export const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = config.cors?.allowedOrigins || [
'http://localhost:8080',
'http://localhost:3000',
'https://app.marketing-agent.com'
];
// Allow requests with no origin (like mobile apps or Postman)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf('*') !== -1 || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
logger.warn('CORS blocked request', { origin, ip: origin });
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key', 'X-Request-ID'],
exposedHeaders: ['X-Request-ID', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset'],
maxAge: 86400 // 24 hours
};
/**
* Configure Helmet security headers
*/
export const helmetConfig = helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
upgradeInsecureRequests: config.environment === 'production' ? [] : null
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
});
/**
* Request ID middleware
*/
export function requestId(req, res, next) {
const id = req.get('X-Request-ID') || crypto.randomUUID();
req.id = id;
res.setHeader('X-Request-ID', id);
next();
}
/**
* IP whitelist/blacklist middleware
*/
export function ipFilter(options = {}) {
const whitelist = options.whitelist || [];
const blacklist = options.blacklist || [];
return (req, res, next) => {
const clientIp = req.ip || req.connection.remoteAddress;
// Check blacklist first
if (blacklist.length > 0 && blacklist.includes(clientIp)) {
logger.warn('Blacklisted IP attempted access', { ip: clientIp });
return res.status(403).json({
success: false,
error: 'Access denied'
});
}
// Check whitelist if configured
if (whitelist.length > 0 && !whitelist.includes(clientIp)) {
logger.warn('Non-whitelisted IP attempted access', { ip: clientIp });
return res.status(403).json({
success: false,
error: 'Access denied'
});
}
next();
};
}
/**
* Security headers middleware
*/
export function securityHeaders(req, res, next) {
// Additional security headers
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
// Remove potentially sensitive headers
res.removeHeader('X-Powered-By');
res.removeHeader('Server');
next();
}
/**
* API version check middleware
*/
export function apiVersionCheck(supportedVersions = ['v1']) {
return (req, res, next) => {
const version = req.headers['api-version'] || req.query.apiVersion;
if (version && !supportedVersions.includes(version)) {
return res.status(400).json({
success: false,
error: 'Unsupported API version',
supportedVersions
});
}
req.apiVersion = version || supportedVersions[0];
next();
};
}
/**
* Prevent parameter pollution
*/
export const preventParamPollution = hpp({
whitelist: ['sort', 'fields', 'filter', 'page', 'limit']
});
/**
* MongoDB injection prevention
*/
export const preventMongoInjection = mongoSanitize({
replaceWith: '_',
onSanitize: ({ req, key }) => {
logger.warn('MongoDB injection attempt prevented', {
ip: req.ip,
path: req.path,
key
});
}
});
/**
* Request logging middleware
*/
export function requestLogger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('Request processed', {
requestId: req.id,
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration,
ip: req.ip,
userAgent: req.get('user-agent'),
userId: req.user?.id
});
});
next();
}
/**
* Error logging middleware
*/
export function errorLogger(err, req, res, next) {
logger.error('Request error', {
requestId: req.id,
error: err.message,
stack: err.stack,
method: req.method,
path: req.path,
ip: req.ip,
userId: req.user?.id
});
next(err);
}
/**
* Security audit middleware
*/
export function securityAudit(eventType) {
return (req, res, next) => {
logger.info('Security event', {
eventType,
requestId: req.id,
userId: req.user?.id,
ip: req.ip,
path: req.path,
method: req.method,
timestamp: new Date().toISOString()
});
next();
};
}
/**
* Trusted proxy configuration
*/
export function configureTrustedProxies(app) {
// Trust proxies for accurate IP detection
app.set('trust proxy', config.trustProxy || ['loopback', 'linklocal', 'uniquelocal']);
}
/**
* Session fixation prevention
*/
export function preventSessionFixation(req, res, next) {
if (req.session && req.user) {
// Regenerate session ID on login
if (req.path.includes('/login') && res.statusCode === 200) {
req.session.regenerate((err) => {
if (err) {
logger.error('Session regeneration failed', { error: err });
}
next();
});
return;
}
}
next();
}
/**
* CSRF protection for state-changing operations
*/
export function csrfProtection(options = {}) {
const excludePaths = options.exclude || ['/api/v1/auth/login', '/api/v1/auth/register'];
return (req, res, next) => {
// Skip for excluded paths
if (excludePaths.some(path => req.path.includes(path))) {
return next();
}
// Skip for safe methods
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const token = req.headers['x-csrf-token'] || req.body._csrf;
const sessionToken = req.session?.csrfToken;
if (!token || !sessionToken || token !== sessionToken) {
return res.status(403).json({
success: false,
error: 'Invalid CSRF token'
});
}
next();
};
}
/**
* Apply all security middleware
*/
export function applySecurityMiddleware(app) {
// Basic security
app.use(helmetConfig);
app.use(cors(corsOptions));
app.use(requestId);
app.use(securityHeaders);
// Request processing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Injection prevention
app.use(preventMongoInjection);
app.use(preventParamPollution);
// Logging
app.use(requestLogger);
// Configure trusted proxies
configureTrustedProxies(app);
}