235 lines
17 KiB
JavaScript
235 lines
17 KiB
JavaScript
|
|
import * as plugins from '../plugins.js';
|
||
|
|
import { logger } from '../logger.js';
|
||
|
|
/**
|
||
|
|
* Log level for security events
|
||
|
|
*/
|
||
|
|
export var SecurityLogLevel;
|
||
|
|
(function (SecurityLogLevel) {
|
||
|
|
SecurityLogLevel["INFO"] = "info";
|
||
|
|
SecurityLogLevel["WARN"] = "warn";
|
||
|
|
SecurityLogLevel["ERROR"] = "error";
|
||
|
|
SecurityLogLevel["CRITICAL"] = "critical";
|
||
|
|
})(SecurityLogLevel || (SecurityLogLevel = {}));
|
||
|
|
/**
|
||
|
|
* Security event types for categorization
|
||
|
|
*/
|
||
|
|
export var SecurityEventType;
|
||
|
|
(function (SecurityEventType) {
|
||
|
|
SecurityEventType["AUTHENTICATION"] = "authentication";
|
||
|
|
SecurityEventType["ACCESS_CONTROL"] = "access_control";
|
||
|
|
SecurityEventType["EMAIL_VALIDATION"] = "email_validation";
|
||
|
|
SecurityEventType["EMAIL_PROCESSING"] = "email_processing";
|
||
|
|
SecurityEventType["EMAIL_FORWARDING"] = "email_forwarding";
|
||
|
|
SecurityEventType["EMAIL_DELIVERY"] = "email_delivery";
|
||
|
|
SecurityEventType["DKIM"] = "dkim";
|
||
|
|
SecurityEventType["SPF"] = "spf";
|
||
|
|
SecurityEventType["DMARC"] = "dmarc";
|
||
|
|
SecurityEventType["RATE_LIMIT"] = "rate_limit";
|
||
|
|
SecurityEventType["RATE_LIMITING"] = "rate_limiting";
|
||
|
|
SecurityEventType["SPAM"] = "spam";
|
||
|
|
SecurityEventType["MALWARE"] = "malware";
|
||
|
|
SecurityEventType["CONNECTION"] = "connection";
|
||
|
|
SecurityEventType["DATA_EXPOSURE"] = "data_exposure";
|
||
|
|
SecurityEventType["CONFIGURATION"] = "configuration";
|
||
|
|
SecurityEventType["IP_REPUTATION"] = "ip_reputation";
|
||
|
|
SecurityEventType["REJECTED_CONNECTION"] = "rejected_connection";
|
||
|
|
})(SecurityEventType || (SecurityEventType = {}));
|
||
|
|
/**
|
||
|
|
* Security logger for enhanced security monitoring
|
||
|
|
*/
|
||
|
|
export class SecurityLogger {
|
||
|
|
static instance;
|
||
|
|
securityEvents = [];
|
||
|
|
maxEventHistory;
|
||
|
|
enableNotifications;
|
||
|
|
constructor(options) {
|
||
|
|
this.maxEventHistory = options?.maxEventHistory || 1000;
|
||
|
|
this.enableNotifications = options?.enableNotifications || false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get singleton instance
|
||
|
|
*/
|
||
|
|
static getInstance(options) {
|
||
|
|
if (!SecurityLogger.instance) {
|
||
|
|
SecurityLogger.instance = new SecurityLogger(options);
|
||
|
|
}
|
||
|
|
return SecurityLogger.instance;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Log a security event
|
||
|
|
* @param event The security event to log
|
||
|
|
*/
|
||
|
|
logEvent(event) {
|
||
|
|
const fullEvent = {
|
||
|
|
...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
|
||
|
|
*/
|
||
|
|
getRecentEvents(limit = 100, filter) {
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
getEventsByLevel(level, limit = 100) {
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
getEventsByType(type, limit = 100) {
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
getEventsByIP(ipAddress, limit = 100) {
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
getEventsByDomain(domain, limit = 100) {
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
sendNotification(event) {
|
||
|
|
// 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
|
||
|
|
*/
|
||
|
|
clearEvents() {
|
||
|
|
this.securityEvents = [];
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get statistical summary of security events
|
||
|
|
* @param timeWindow Optional time window in milliseconds
|
||
|
|
* @returns Summary of security events
|
||
|
|
*/
|
||
|
|
getEventsSummary(timeWindow) {
|
||
|
|
// 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;
|
||
|
|
}, {});
|
||
|
|
// Count by type
|
||
|
|
const byType = Object.values(SecurityEventType).reduce((acc, type) => {
|
||
|
|
acc[type] = events.filter(e => e.type === type).length;
|
||
|
|
return acc;
|
||
|
|
}, {});
|
||
|
|
// Count by IP
|
||
|
|
const ipCounts = new Map();
|
||
|
|
events.forEach(e => {
|
||
|
|
if (e.ipAddress) {
|
||
|
|
ipCounts.set(e.ipAddress, (ipCounts.get(e.ipAddress) || 0) + 1);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
// Count by domain
|
||
|
|
const domainCounts = new Map();
|
||
|
|
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
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zZWN1cml0eWxvZ2dlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3NlY3VyaXR5L2NsYXNzZXMuc2VjdXJpdHlsb2dnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUV0Qzs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLGdCQUtYO0FBTEQsV0FBWSxnQkFBZ0I7SUFDMUIsaUNBQWEsQ0FBQTtJQUNiLGlDQUFhLENBQUE7SUFDYixtQ0FBZSxDQUFBO0lBQ2YseUNBQXFCLENBQUE7QUFDdkIsQ0FBQyxFQUxXLGdCQUFnQixLQUFoQixnQkFBZ0IsUUFLM0I7QUFFRDs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLGlCQW1CWDtBQW5CRCxXQUFZLGlCQUFpQjtJQUMzQixzREFBaUMsQ0FBQTtJQUNqQyxzREFBaUMsQ0FBQTtJQUNqQywwREFBcUMsQ0FBQTtJQUNyQywwREFBcUMsQ0FBQTtJQUNyQywwREFBcUMsQ0FBQTtJQUNyQyxzREFBaUMsQ0FBQTtJQUNqQyxrQ0FBYSxDQUFBO0lBQ2IsZ0NBQVcsQ0FBQTtJQUNYLG9DQUFlLENBQUE7SUFDZiw4Q0FBeUIsQ0FBQTtJQUN6QixvREFBK0IsQ0FBQTtJQUMvQixrQ0FBYSxDQUFBO0lBQ2Isd0NBQW1CLENBQUE7SUFDbkIsOENBQXlCLENBQUE7SUFDekIsb0RBQStCLENBQUE7SUFDL0Isb0RBQStCLENBQUE7SUFDL0Isb0RBQStCLENBQUE7SUFDL0IsZ0VBQTJDLENBQUE7QUFDN0MsQ0FBQyxFQW5CVyxpQkFBaUIsS0FBakIsaUJBQWlCLFFBbUI1QjtBQXFCRDs7R0FFRztBQUNILE1BQU0sT0FBTyxjQUFjO0lBQ2pCLE1BQU0sQ0FBQyxRQUFRLENBQWlCO0lBQ2hDLGNBQWMsR0FBcUIsRUFBRSxDQUFDO0lBQ3RDLGVBQWUsQ0FB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
|