feat: implement security logging system
This commit is contained in:
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user