const fs = require('fs').promises; const path = require('path'); class SecurityLogger { constructor() { this.logFile = path.join(process.cwd(), 'security.log'); this.maxLogSize = 10 * 1024 * 1024; // 10MB this.maxLogFiles = 5; } async log(level, event, details = {}, req = null) { const timestamp = new Date().toISOString(); // Extract safe request information const requestInfo = req ? { ip: req.ip || req.connection.remoteAddress, userAgent: req.get('User-Agent'), method: req.method, url: req.originalUrl || req.url, userId: req.session?.user?.id, username: req.session?.user?.username } : {}; const logEntry = { timestamp, level, event, details, request: requestInfo, pid: process.pid }; const logLine = JSON.stringify(logEntry) + '\n'; try { // Check if log rotation is needed await this.rotateLogIfNeeded(); // Append to log file await fs.appendFile(this.logFile, logLine); // Also log to console for development if (process.env.NODE_ENV !== 'production') { console.log(`[SECURITY] ${level.toUpperCase()}: ${event}`, details); } } catch (error) { console.error('Failed to write security log:', error); } } async rotateLogIfNeeded() { try { const stats = await fs.stat(this.logFile); if (stats.size > this.maxLogSize) { // Rotate logs for (let i = this.maxLogFiles - 1; i > 0; i--) { const oldFile = `${this.logFile}.${i}`; const newFile = `${this.logFile}.${i + 1}`; try { await fs.rename(oldFile, newFile); } catch (error) { // File might not exist, continue } } // Move current log to .1 await fs.rename(this.logFile, `${this.logFile}.1`); } } catch (error) { // Log file might not exist yet, that's fine } } // Security event logging methods async logAuthSuccess(req, username) { await this.log('info', 'AUTH_SUCCESS', { username, sessionId: req.sessionID }, req); } async logAuthFailure(req, username, reason) { await this.log('warn', 'AUTH_FAILURE', { username, reason, sessionId: req.sessionID }, req); } async logCommandExecution(req, command, result) { await this.log('info', 'COMMAND_EXECUTION', { command, result: result ? 'success' : 'failed' }, req); } async logConfigChange(req, section, changes) { await this.log('info', 'CONFIG_CHANGE', { section, changes: Object.keys(changes) }, req); } async logSecurityViolation(req, violationType, details) { await this.log('error', 'SECURITY_VIOLATION', { violationType, details }, req); } async logServerStart(req, worldName, options = {}) { await this.log('info', 'SERVER_START', { worldName, options }, req); } async logServerStop(req, forced = false) { await this.log('info', 'SERVER_STOP', { forced }, req); } async logFileAccess(req, filePath, operation) { await this.log('info', 'FILE_ACCESS', { filePath, operation }, req); } async logSuspiciousActivity(req, activityType, details) { await this.log('warn', 'SUSPICIOUS_ACTIVITY', { activityType, details }, req); } async logRateLimitExceeded(req) { await this.log('warn', 'RATE_LIMIT_EXCEEDED', { limit: 'request_rate' }, req); } async logCSRFViolation(req) { await this.log('error', 'CSRF_VIOLATION', { referer: req.get('Referer'), origin: req.get('Origin') }, req); } async logInputValidationFailure(req, field, value, reason) { await this.log('warn', 'INPUT_VALIDATION_FAILURE', { field, valueLength: value ? value.length : 0, reason }, req); } // Read security logs (for admin interface) async getRecentLogs(limit = 100) { try { const content = await fs.readFile(this.logFile, 'utf-8'); const lines = content.trim().split('\n').filter(line => line); return lines.slice(-limit).map(line => { try { return JSON.parse(line); } catch { return { error: 'Failed to parse log line', line }; } }).reverse(); // Most recent first } catch (error) { return []; } } // Get security metrics async getSecurityMetrics(hours = 24) { const logs = await this.getRecentLogs(10000); // Large sample const since = new Date(Date.now() - hours * 60 * 60 * 1000); const recentLogs = logs.filter(log => log.timestamp && new Date(log.timestamp) > since ); const metrics = { totalEvents: recentLogs.length, authFailures: recentLogs.filter(log => log.event === 'AUTH_FAILURE').length, securityViolations: recentLogs.filter(log => log.event === 'SECURITY_VIOLATION').length, suspiciousActivity: recentLogs.filter(log => log.event === 'SUSPICIOUS_ACTIVITY').length, rateLimitExceeded: recentLogs.filter(log => log.event === 'RATE_LIMIT_EXCEEDED').length, csrfViolations: recentLogs.filter(log => log.event === 'CSRF_VIOLATION').length, commandExecutions: recentLogs.filter(log => log.event === 'COMMAND_EXECUTION').length, configChanges: recentLogs.filter(log => log.event === 'CONFIG_CHANGE').length }; return metrics; } } // Singleton instance const securityLogger = new SecurityLogger(); module.exports = securityLogger;