feat: implement security logging system

This commit is contained in:
张翔
2026-03-24 10:45:00 +08:00
parent 0d020bc3f8
commit b542f922b2
2 changed files with 156 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
import { SecurityLogger, SecurityEventType } from './logger';
describe('Security Logging System', () => {
let logger: SecurityLogger;
beforeEach(() => {
logger = new SecurityLogger();
});
test('should log security events', () => {
logger.logEvent({
type: SecurityEventType.CAPTCHA_FAILED,
ip: '192.168.1.1',
email: 'test@example.com',
details: { reason: 'Invalid answer' },
});
const logs = logger.getRecentLogs(10);
expect(logs.length).toBe(1);
expect(logs[0].type).toBe(SecurityEventType.CAPTCHA_FAILED);
});
test('should detect suspicious activity', () => {
for (let i = 0; i < 6; i++) {
logger.logEvent({
type: SecurityEventType.CAPTCHA_FAILED,
ip: '192.168.1.2',
email: 'suspicious@example.com',
details: {},
});
}
const suspiciousIPs = logger.getSuspiciousIPs();
expect(suspiciousIPs).toContain('192.168.1.2');
});
test('should clear old logs', () => {
logger.logEvent({
type: SecurityEventType.RATE_LIMIT_EXCEEDED,
ip: '192.168.1.3',
details: {},
});
logger.clearOldLogs(0);
const logs = logger.getRecentLogs(10);
expect(logs.length).toBe(0);
});
});
+107
View File
@@ -0,0 +1,107 @@
export enum SecurityEventType {
CAPTCHA_FAILED = 'captcha_failed',
CAPTCHA_EXPIRED = 'captcha_expired',
RATE_LIMIT_EXCEEDED = 'rate_limit_exceeded',
MALICIOUS_CONTENT = 'malicious_content',
XSS_ATTEMPT = 'xss_attempt',
SQL_INJECTION_ATTEMPT = 'sql_injection_attempt',
SUSPICIOUS_IP = 'suspicious_ip',
BLOCKED_REQUEST = 'blocked_request',
}
export interface SecurityEvent {
type: SecurityEventType;
timestamp: number;
ip?: string;
email?: string;
userAgent?: string;
details?: Record<string, any>;
}
export interface SuspiciousActivity {
ip: string;
eventCount: number;
lastSeen: number;
eventTypes: SecurityEventType[];
}
class InMemorySecurityLogger {
private events: SecurityEvent[] = [];
private ipActivity = new Map<string, number>();
logEvent(event: Omit<SecurityEvent, 'timestamp'>): void {
const fullEvent: SecurityEvent = {
...event,
timestamp: Date.now(),
};
this.events.push(fullEvent);
if (event.ip) {
const currentCount = this.ipActivity.get(event.ip) || 0;
this.ipActivity.set(event.ip, currentCount + 1);
}
console.log(`[Security] ${event.type}:`, event);
}
getRecentLogs(limit: number = 100): SecurityEvent[] {
return this.events.slice(-limit);
}
getSuspiciousIPs(threshold: number = 5): string[] {
const suspicious: string[] = [];
for (const [ip, count] of this.ipActivity.entries()) {
if (count >= threshold) {
suspicious.push(ip);
}
}
return suspicious;
}
clearOldLogs(daysToKeep: number = 30): void {
const cutoffTime = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);
this.events = this.events.filter(event => event.timestamp > cutoffTime);
}
getActivityByIP(ip: string): number {
return this.ipActivity.get(ip) || 0;
}
clear(): void {
this.events = [];
this.ipActivity.clear();
}
}
export class SecurityLogger {
private logger: InMemorySecurityLogger;
constructor() {
this.logger = new InMemorySecurityLogger();
}
logEvent(event: Omit<SecurityEvent, 'timestamp'>): void {
this.logger.logEvent(event);
}
getRecentLogs(limit?: number): SecurityEvent[] {
return this.logger.getRecentLogs(limit);
}
getSuspiciousIPs(threshold?: number): string[] {
return this.logger.getSuspiciousIPs(threshold);
}
clearOldLogs(daysToKeep?: number): void {
this.logger.clearOldLogs(daysToKeep);
}
getActivityByIP(ip: string): number {
return this.logger.getActivityByIP(ip);
}
clear(): void {
this.logger.clear();
}
}