294 lines
8.3 KiB
TypeScript
294 lines
8.3 KiB
TypeScript
|
import * as plugins from '../plugins.js';
|
||
|
import { logger } from '../logger.js';
|
||
|
|
||
|
/**
|
||
|
* Log level for security events
|
||
|
*/
|
||
|
export enum SecurityLogLevel {
|
||
|
INFO = 'info',
|
||
|
WARN = 'warn',
|
||
|
ERROR = 'error',
|
||
|
CRITICAL = 'critical'
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Security event types for categorization
|
||
|
*/
|
||
|
export enum SecurityEventType {
|
||
|
AUTHENTICATION = 'authentication',
|
||
|
ACCESS_CONTROL = 'access_control',
|
||
|
EMAIL_VALIDATION = 'email_validation',
|
||
|
DKIM = 'dkim',
|
||
|
SPF = 'spf',
|
||
|
DMARC = 'dmarc',
|
||
|
RATE_LIMIT = 'rate_limit',
|
||
|
SPAM = 'spam',
|
||
|
MALWARE = 'malware',
|
||
|
CONNECTION = 'connection',
|
||
|
DATA_EXPOSURE = 'data_exposure',
|
||
|
CONFIGURATION = 'configuration',
|
||
|
IP_REPUTATION = 'ip_reputation'
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Security event interface
|
||
|
*/
|
||
|
export interface ISecurityEvent {
|
||
|
timestamp: number;
|
||
|
level: SecurityLogLevel;
|
||
|
type: SecurityEventType;
|
||
|
message: string;
|
||
|
details?: any;
|
||
|
ipAddress?: string;
|
||
|
userId?: string;
|
||
|
sessionId?: string;
|
||
|
emailId?: string;
|
||
|
domain?: string;
|
||
|
action?: string;
|
||
|
result?: string;
|
||
|
success?: boolean;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Security logger for enhanced security monitoring
|
||
|
*/
|
||
|
export class SecurityLogger {
|
||
|
private static instance: SecurityLogger;
|
||
|
private securityEvents: ISecurityEvent[] = [];
|
||
|
private maxEventHistory: number;
|
||
|
private enableNotifications: boolean;
|
||
|
|
||
|
private constructor(options?: {
|
||
|
maxEventHistory?: number;
|
||
|
enableNotifications?: boolean;
|
||
|
}) {
|
||
|
this.maxEventHistory = options?.maxEventHistory || 1000;
|
||
|
this.enableNotifications = options?.enableNotifications || false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get singleton instance
|
||
|
*/
|
||
|
public static getInstance(options?: {
|
||
|
maxEventHistory?: number;
|
||
|
enableNotifications?: boolean;
|
||
|
}): SecurityLogger {
|
||
|
if (!SecurityLogger.instance) {
|
||
|
SecurityLogger.instance = new SecurityLogger(options);
|
||
|
}
|
||
|
return SecurityLogger.instance;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Log a security event
|
||
|
* @param event The security event to log
|
||
|
*/
|
||
|
public logEvent(event: Omit<ISecurityEvent, 'timestamp'>): void {
|
||
|
const fullEvent: ISecurityEvent = {
|
||
|
...event,
|
||
|
timestamp: Date.now()
|
||
|
};
|
||
|
|
||
|
// Store in memory buffer
|
||
|
this.securityEvents.push(fullEvent);
|
||
|
|
||
|
// Trim history if needed
|
||
|
if (this.securityEvents.length > this.maxEventHistory) {
|
||
|
this.securityEvents.shift();
|
||
|
}
|
||
|
|
||
|
// Log to regular logger with appropriate level
|
||
|
switch (event.level) {
|
||
|
case SecurityLogLevel.INFO:
|
||
|
logger.log('info', `[SECURITY:${event.type}] ${event.message}`, event.details);
|
||
|
break;
|
||
|
case SecurityLogLevel.WARN:
|
||
|
logger.log('warn', `[SECURITY:${event.type}] ${event.message}`, event.details);
|
||
|
break;
|
||
|
case SecurityLogLevel.ERROR:
|
||
|
case SecurityLogLevel.CRITICAL:
|
||
|
logger.log('error', `[SECURITY:${event.type}] ${event.message}`, event.details);
|
||
|
|
||
|
// Send notification for critical events if enabled
|
||
|
if (event.level === SecurityLogLevel.CRITICAL && this.enableNotifications) {
|
||
|
this.sendNotification(fullEvent);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get recent security events
|
||
|
* @param limit Maximum number of events to return
|
||
|
* @param filter Filter for specific event types
|
||
|
* @returns Recent security events
|
||
|
*/
|
||
|
public getRecentEvents(limit: number = 100, filter?: {
|
||
|
level?: SecurityLogLevel;
|
||
|
type?: SecurityEventType;
|
||
|
fromTimestamp?: number;
|
||
|
toTimestamp?: number;
|
||
|
}): ISecurityEvent[] {
|
||
|
let filteredEvents = this.securityEvents;
|
||
|
|
||
|
// Apply filters
|
||
|
if (filter) {
|
||
|
if (filter.level) {
|
||
|
filteredEvents = filteredEvents.filter(event => event.level === filter.level);
|
||
|
}
|
||
|
|
||
|
if (filter.type) {
|
||
|
filteredEvents = filteredEvents.filter(event => event.type === filter.type);
|
||
|
}
|
||
|
|
||
|
if (filter.fromTimestamp) {
|
||
|
filteredEvents = filteredEvents.filter(event => event.timestamp >= filter.fromTimestamp);
|
||
|
}
|
||
|
|
||
|
if (filter.toTimestamp) {
|
||
|
filteredEvents = filteredEvents.filter(event => event.timestamp <= filter.toTimestamp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return most recent events up to limit
|
||
|
return filteredEvents
|
||
|
.sort((a, b) => b.timestamp - a.timestamp)
|
||
|
.slice(0, limit);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get events by security level
|
||
|
* @param level The security level to filter by
|
||
|
* @param limit Maximum number of events to return
|
||
|
* @returns Security events matching the level
|
||
|
*/
|
||
|
public getEventsByLevel(level: SecurityLogLevel, limit: number = 100): ISecurityEvent[] {
|
||
|
return this.getRecentEvents(limit, { level });
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get events by security type
|
||
|
* @param type The event type to filter by
|
||
|
* @param limit Maximum number of events to return
|
||
|
* @returns Security events matching the type
|
||
|
*/
|
||
|
public getEventsByType(type: SecurityEventType, limit: number = 100): ISecurityEvent[] {
|
||
|
return this.getRecentEvents(limit, { type });
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get security events for a specific IP address
|
||
|
* @param ipAddress The IP address to filter by
|
||
|
* @param limit Maximum number of events to return
|
||
|
* @returns Security events for the IP address
|
||
|
*/
|
||
|
public getEventsByIP(ipAddress: string, limit: number = 100): ISecurityEvent[] {
|
||
|
return this.securityEvents
|
||
|
.filter(event => event.ipAddress === ipAddress)
|
||
|
.sort((a, b) => b.timestamp - a.timestamp)
|
||
|
.slice(0, limit);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get security events for a specific domain
|
||
|
* @param domain The domain to filter by
|
||
|
* @param limit Maximum number of events to return
|
||
|
* @returns Security events for the domain
|
||
|
*/
|
||
|
public getEventsByDomain(domain: string, limit: number = 100): ISecurityEvent[] {
|
||
|
return this.securityEvents
|
||
|
.filter(event => event.domain === domain)
|
||
|
.sort((a, b) => b.timestamp - a.timestamp)
|
||
|
.slice(0, limit);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a notification for critical security events
|
||
|
* @param event The security event to notify about
|
||
|
* @private
|
||
|
*/
|
||
|
private sendNotification(event: ISecurityEvent): void {
|
||
|
// In a production environment, this would integrate with a notification service
|
||
|
// For now, we'll just log that we would send a notification
|
||
|
logger.log('error', `[SECURITY NOTIFICATION] ${event.message}`, {
|
||
|
...event,
|
||
|
notificationSent: true
|
||
|
});
|
||
|
|
||
|
// Future integration with alerting systems would go here
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clear event history
|
||
|
*/
|
||
|
public clearEvents(): void {
|
||
|
this.securityEvents = [];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get statistical summary of security events
|
||
|
* @param timeWindow Optional time window in milliseconds
|
||
|
* @returns Summary of security events
|
||
|
*/
|
||
|
public getEventsSummary(timeWindow?: number): {
|
||
|
total: number;
|
||
|
byLevel: Record<SecurityLogLevel, number>;
|
||
|
byType: Record<SecurityEventType, number>;
|
||
|
topIPs: Array<{ ip: string; count: number }>;
|
||
|
topDomains: Array<{ domain: string; count: number }>;
|
||
|
} {
|
||
|
// Filter by time window if provided
|
||
|
let events = this.securityEvents;
|
||
|
if (timeWindow) {
|
||
|
const cutoff = Date.now() - timeWindow;
|
||
|
events = events.filter(e => e.timestamp >= cutoff);
|
||
|
}
|
||
|
|
||
|
// Count by level
|
||
|
const byLevel = Object.values(SecurityLogLevel).reduce((acc, level) => {
|
||
|
acc[level] = events.filter(e => e.level === level).length;
|
||
|
return acc;
|
||
|
}, {} as Record<SecurityLogLevel, number>);
|
||
|
|
||
|
// Count by type
|
||
|
const byType = Object.values(SecurityEventType).reduce((acc, type) => {
|
||
|
acc[type] = events.filter(e => e.type === type).length;
|
||
|
return acc;
|
||
|
}, {} as Record<SecurityEventType, number>);
|
||
|
|
||
|
// Count by IP
|
||
|
const ipCounts = new Map<string, number>();
|
||
|
events.forEach(e => {
|
||
|
if (e.ipAddress) {
|
||
|
ipCounts.set(e.ipAddress, (ipCounts.get(e.ipAddress) || 0) + 1);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Count by domain
|
||
|
const domainCounts = new Map<string, number>();
|
||
|
events.forEach(e => {
|
||
|
if (e.domain) {
|
||
|
domainCounts.set(e.domain, (domainCounts.get(e.domain) || 0) + 1);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Sort and limit top entries
|
||
|
const topIPs = Array.from(ipCounts.entries())
|
||
|
.map(([ip, count]) => ({ ip, count }))
|
||
|
.sort((a, b) => b.count - a.count)
|
||
|
.slice(0, 10);
|
||
|
|
||
|
const topDomains = Array.from(domainCounts.entries())
|
||
|
.map(([domain, count]) => ({ domain, count }))
|
||
|
.sort((a, b) => b.count - a.count)
|
||
|
.slice(0, 10);
|
||
|
|
||
|
return {
|
||
|
total: events.length,
|
||
|
byLevel,
|
||
|
byType,
|
||
|
topIPs,
|
||
|
topDomains
|
||
|
};
|
||
|
}
|
||
|
}
|