228 lines
6.5 KiB
JavaScript
228 lines
6.5 KiB
JavaScript
/**
|
|
* Audit Logger
|
|
*
|
|
* Comprehensive audit logging for security and compliance:
|
|
* - All API requests
|
|
* - Authentication events
|
|
* - Security violations
|
|
* - Performance metrics
|
|
*/
|
|
/**
|
|
* Audit Logger
|
|
*
|
|
* Logs all security-relevant events:
|
|
* - API requests and responses
|
|
* - Authentication attempts
|
|
* - Rate limit violations
|
|
* - Input validation failures
|
|
* - Circuit breaker state changes
|
|
*/
|
|
export class AuditLogger {
|
|
config;
|
|
memoryLog;
|
|
static instance;
|
|
constructor(config) {
|
|
this.config = {
|
|
enableConsole: config?.enableConsole ?? true,
|
|
enableFile: config?.enableFile ?? false,
|
|
logFilePath: config?.logFilePath ?? './logs/audit.log',
|
|
minSeverity: config?.minSeverity ?? 'INFO',
|
|
maxMemoryEntries: config?.maxMemoryEntries ?? 1000,
|
|
};
|
|
this.memoryLog = [];
|
|
}
|
|
/**
|
|
* Get singleton instance
|
|
*/
|
|
static getInstance(config) {
|
|
if (!AuditLogger.instance) {
|
|
AuditLogger.instance = new AuditLogger(config);
|
|
}
|
|
return AuditLogger.instance;
|
|
}
|
|
/**
|
|
* Log event
|
|
*
|
|
* @param event - Audit event to log
|
|
*/
|
|
log(event) {
|
|
// Check severity filter
|
|
const severities = ['INFO', 'WARNING', 'ERROR', 'CRITICAL'];
|
|
const minIndex = severities.indexOf(this.config.minSeverity);
|
|
const eventIndex = severities.indexOf(event.severity);
|
|
if (eventIndex < minIndex) {
|
|
return;
|
|
}
|
|
// Add to memory log
|
|
this.memoryLog.push(event);
|
|
if (this.memoryLog.length > this.config.maxMemoryEntries) {
|
|
this.memoryLog.shift();
|
|
}
|
|
// Console logging
|
|
if (this.config.enableConsole) {
|
|
this.logToConsole(event);
|
|
}
|
|
// File logging (in production, use Winston or Pino)
|
|
if (this.config.enableFile) {
|
|
this.logToFile(event);
|
|
}
|
|
}
|
|
/**
|
|
* Log API request
|
|
*/
|
|
logRequest(req, res, latencyMs) {
|
|
this.log({
|
|
timestamp: Date.now(),
|
|
eventType: 'REQUEST',
|
|
severity: res.statusCode >= 500 ? 'ERROR' : res.statusCode >= 400 ? 'WARNING' : 'INFO',
|
|
userId: req.user?.id,
|
|
ip: req.ip || req.connection.remoteAddress,
|
|
method: req.method,
|
|
path: req.path,
|
|
statusCode: res.statusCode,
|
|
latencyMs,
|
|
message: `${req.method} ${req.path} - ${res.statusCode} (${latencyMs}ms)`,
|
|
metadata: {
|
|
userAgent: req.headers['user-agent'],
|
|
query: req.query,
|
|
},
|
|
});
|
|
}
|
|
/**
|
|
* Log authentication event
|
|
*/
|
|
logAuth(success, userId, ip, reason) {
|
|
this.log({
|
|
timestamp: Date.now(),
|
|
eventType: 'AUTH',
|
|
severity: success ? 'INFO' : 'WARNING',
|
|
userId,
|
|
ip,
|
|
message: success
|
|
? `Authentication successful for user ${userId}`
|
|
: `Authentication failed: ${reason}`,
|
|
metadata: { success, reason },
|
|
});
|
|
}
|
|
/**
|
|
* Log security violation
|
|
*/
|
|
logSecurityViolation(type, userId, ip, details) {
|
|
this.log({
|
|
timestamp: Date.now(),
|
|
eventType: 'SECURITY',
|
|
severity: 'WARNING',
|
|
userId,
|
|
ip,
|
|
message: `Security violation: ${type}`,
|
|
metadata: { type, details },
|
|
});
|
|
}
|
|
/**
|
|
* Log error
|
|
*/
|
|
logError(error, userId, ip, context) {
|
|
this.log({
|
|
timestamp: Date.now(),
|
|
eventType: 'ERROR',
|
|
severity: 'ERROR',
|
|
userId,
|
|
ip,
|
|
message: error.message,
|
|
metadata: {
|
|
error: error.name,
|
|
stack: error.stack,
|
|
...context,
|
|
},
|
|
});
|
|
}
|
|
/**
|
|
* Get recent logs
|
|
*/
|
|
getRecentLogs(limit = 100) {
|
|
return this.memoryLog.slice(-limit);
|
|
}
|
|
/**
|
|
* Get logs by user
|
|
*/
|
|
getLogsByUser(userId, limit = 100) {
|
|
return this.memoryLog
|
|
.filter(event => event.userId === userId)
|
|
.slice(-limit);
|
|
}
|
|
/**
|
|
* Get logs by type
|
|
*/
|
|
getLogsByType(eventType, limit = 100) {
|
|
return this.memoryLog
|
|
.filter(event => event.eventType === eventType)
|
|
.slice(-limit);
|
|
}
|
|
/**
|
|
* Log to console
|
|
*/
|
|
logToConsole(event) {
|
|
const timestamp = new Date(event.timestamp).toISOString();
|
|
const prefix = `[${timestamp}] [${event.severity}] [${event.eventType}]`;
|
|
const logFn = event.severity === 'ERROR' || event.severity === 'CRITICAL'
|
|
? console.error
|
|
: event.severity === 'WARNING'
|
|
? console.warn
|
|
: console.log;
|
|
logFn(`${prefix} ${event.message}`);
|
|
if (event.metadata) {
|
|
logFn(' Metadata:', event.metadata);
|
|
}
|
|
}
|
|
/**
|
|
* Log to file (placeholder - use Winston/Pino in production)
|
|
*/
|
|
logToFile(event) {
|
|
// In production, use Winston or Pino for file logging
|
|
// This is a placeholder
|
|
const line = JSON.stringify(event) + '\n';
|
|
// fs.appendFileSync(this.config.logFilePath, line);
|
|
}
|
|
/**
|
|
* Clear memory logs
|
|
*/
|
|
clear() {
|
|
this.memoryLog = [];
|
|
}
|
|
/**
|
|
* Get statistics
|
|
*/
|
|
getStats() {
|
|
const stats = {
|
|
totalEvents: this.memoryLog.length,
|
|
byType: {},
|
|
bySeverity: {},
|
|
errorRate: 0,
|
|
};
|
|
for (const event of this.memoryLog) {
|
|
stats.byType[event.eventType] = (stats.byType[event.eventType] || 0) + 1;
|
|
stats.bySeverity[event.severity] = (stats.bySeverity[event.severity] || 0) + 1;
|
|
}
|
|
const errorCount = (stats.bySeverity['ERROR'] || 0) + (stats.bySeverity['CRITICAL'] || 0);
|
|
stats.errorRate = stats.totalEvents > 0 ? errorCount / stats.totalEvents : 0;
|
|
return stats;
|
|
}
|
|
}
|
|
/**
|
|
* Create audit logging middleware
|
|
*/
|
|
export function createAuditMiddleware(logger) {
|
|
const auditLogger = logger || AuditLogger.getInstance();
|
|
return (req, res, next) => {
|
|
const startTime = Date.now();
|
|
// Log after response finishes
|
|
res.on('finish', () => {
|
|
const latencyMs = Date.now() - startTime;
|
|
auditLogger.logRequest(req, res, latencyMs);
|
|
});
|
|
next();
|
|
};
|
|
}
|
|
// Export singleton
|
|
export const auditLogger = AuditLogger.getInstance();
|
|
//# sourceMappingURL=audit-logger.js.map
|