feat: implement security middleware

This commit is contained in:
张翔
2026-03-24 10:53:37 +08:00
parent b542f922b2
commit 60f3f371bb
2 changed files with 208 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
import { SecurityMiddleware, SecurityCheckResult } from './middleware';
import { generateCaptcha } from './captcha';
describe('Security Middleware', () => {
let middleware: SecurityMiddleware;
beforeEach(() => {
middleware = new SecurityMiddleware();
});
test('should pass valid request', async () => {
const captcha = generateCaptcha('simple');
const result = await middleware.checkRequest({
ip: '192.168.1.1',
email: 'valid@example.com',
captchaHash: captcha.hash,
captchaAnswer: captcha.answer,
captchaTimestamp: captcha.timestamp,
});
expect(result.allowed).toBe(true);
expect(result.errors).toHaveLength(0);
});
test('should block request with invalid captcha', async () => {
const captcha = generateCaptcha('simple');
const result = await middleware.checkRequest({
ip: '192.168.1.2',
email: 'test@example.com',
captchaHash: captcha.hash,
captchaAnswer: 999,
captchaTimestamp: captcha.timestamp,
});
expect(result.allowed).toBe(false);
expect(result.errors).toContain('Invalid captcha');
});
test('should block request exceeding rate limit', async () => {
const ip = '192.168.1.3';
for (let i = 0; i < 11; i++) {
const captcha = generateCaptcha('simple');
await middleware.checkRequest({
ip,
email: `test${i}@example.com`,
captchaHash: captcha.hash,
captchaAnswer: captcha.answer,
captchaTimestamp: captcha.timestamp,
});
}
const captcha = generateCaptcha('simple');
const result = await middleware.checkRequest({
ip,
email: 'test@example.com',
captchaHash: captcha.hash,
captchaAnswer: captcha.answer,
captchaTimestamp: captcha.timestamp,
});
expect(result.allowed).toBe(false);
expect(result.errors).toContain('Rate limit exceeded for IP');
});
test('should block request with malicious content', async () => {
const captcha = generateCaptcha('simple');
const result = await middleware.checkRequest({
ip: '192.168.1.4',
email: 'test@example.com',
captchaHash: captcha.hash,
captchaAnswer: captcha.answer,
captchaTimestamp: captcha.timestamp,
message: 'You won a free bitcoin lottery! Act now to claim your prize.',
});
expect(result.allowed).toBe(false);
expect(result.errors.some(e => e.includes('malicious'))).toBe(true);
});
});
+129
View File
@@ -0,0 +1,129 @@
import { RateLimiter } from './rate-limiter';
import { validateCaptcha } from './captcha';
import { sanitizeFormData, detectMaliciousContent } from './sanitizer';
import { SecurityLogger, SecurityEventType } from './logger';
import { getSecurityConfig } from './config';
export interface SecurityCheckRequest {
ip: string;
email?: string;
name?: string;
phone?: string;
subject?: string;
message?: string;
captchaHash: string;
captchaAnswer: number;
captchaTimestamp: number;
userAgent?: string;
}
export interface SecurityCheckResult {
allowed: boolean;
errors: string[];
warnings: string[];
}
export class SecurityMiddleware {
private rateLimiter: RateLimiter;
private logger: SecurityLogger;
private config: any;
constructor() {
this.rateLimiter = new RateLimiter();
this.logger = new SecurityLogger();
this.config = getSecurityConfig();
}
async checkRequest(request: SecurityCheckRequest): Promise<SecurityCheckResult> {
const errors: string[] = [];
const warnings: string[] = [];
if (this.config.protection.enableInputSanitization) {
if (request.message && detectMaliciousContent(request.message)) {
errors.push('Message contains malicious content');
this.logger.logEvent({
type: SecurityEventType.MALICIOUS_CONTENT,
ip: request.ip,
email: request.email,
userAgent: request.userAgent,
details: { message: request.message },
});
}
}
if (!validateCaptcha(request.captchaHash, request.captchaAnswer, request.captchaTimestamp)) {
errors.push('Invalid captcha');
this.logger.logEvent({
type: SecurityEventType.CAPTCHA_FAILED,
ip: request.ip,
email: request.email,
userAgent: request.userAgent,
details: { answer: request.captchaAnswer },
});
}
const ipRateLimit = await this.rateLimiter.checkIP(request.ip);
if (!ipRateLimit.allowed) {
errors.push('Rate limit exceeded for IP');
this.logger.logEvent({
type: SecurityEventType.RATE_LIMIT_EXCEEDED,
ip: request.ip,
email: request.email,
details: { limit: ipRateLimit.limit },
});
}
if (request.email) {
const emailRateLimit = await this.rateLimiter.checkEmail(request.email);
if (!emailRateLimit.allowed) {
errors.push('Rate limit exceeded for email');
this.logger.logEvent({
type: SecurityEventType.RATE_LIMIT_EXCEEDED,
ip: request.ip,
email: request.email,
details: { limit: emailRateLimit.limit },
});
}
}
const globalRateLimit = await this.rateLimiter.checkGlobal();
if (!globalRateLimit.allowed) {
warnings.push('Global rate limit exceeded');
}
const suspiciousIPs = this.logger.getSuspiciousIPs();
if (suspiciousIPs.includes(request.ip)) {
errors.push('Suspicious activity detected from this IP');
this.logger.logEvent({
type: SecurityEventType.SUSPICIOUS_IP,
ip: request.ip,
email: request.email,
details: { activityCount: this.logger.getActivityByIP(request.ip) },
});
}
return {
allowed: errors.length === 0,
errors,
warnings,
};
}
sanitizeRequest(request: SecurityCheckRequest): any {
return sanitizeFormData({
name: request.name || '',
email: request.email || '',
phone: request.phone,
subject: request.subject || '',
message: request.message || '',
});
}
getSecurityLogs(limit?: number) {
return this.logger.getRecentLogs(limit);
}
getSuspiciousIPs(threshold?: number) {
return this.logger.getSuspiciousIPs(threshold);
}
}