UNPKG

powerbackup

Version:

Multi-Database Backup & Restore Tool v2.4.2 with Beautiful Logging, REST API, Enhanced Binary Detection & Debug Tools

187 lines (158 loc) 5.97 kB
// Audit logging middleware for PowerBackup API import { log } from '../../utils/logger.js'; import fs from 'fs/promises'; import path from 'path'; /** * Audit logging middleware * Logs all API operations for security and compliance */ export function auditMiddleware(req, res, next) { const startTime = Date.now(); const originalSend = res.send; // Override res.send to capture response res.send = function(data) { const endTime = Date.now(); const duration = endTime - startTime; // Create audit log entry const auditEntry = { timestamp: new Date().toISOString(), method: req.method, path: req.path, ip: req.ip, userAgent: req.get('User-Agent'), statusCode: res.statusCode, duration: duration, user: req.user || null, requestId: req.headers['x-request-id'] || generateRequestId(), body: req.method !== 'GET' ? sanitizeBody(req.body) : null, responseSize: data ? data.length : 0 }; // Log audit entry logAuditEntry(auditEntry); // Call original send return originalSend.call(this, data); }; next(); } /** * Log audit entry to file and console * @param {Object} entry - Audit log entry */ async function logAuditEntry(entry) { try { // Console logging for immediate visibility const logLevel = entry.statusCode >= 400 ? 'error' : 'info'; log[logLevel](`AUDIT: ${entry.method} ${entry.path} - ${entry.statusCode} (${entry.duration}ms) - ${entry.ip}`); // File logging for persistence await writeAuditLog(entry); } catch (error) { log.error('Failed to write audit log:', error.message); } } /** * Write audit log to file * @param {Object} entry - Audit log entry */ async function writeAuditLog(entry) { try { const auditDir = path.resolve(process.cwd(), './logs/audit'); await fs.mkdir(auditDir, { recursive: true }); const date = new Date().toISOString().split('T')[0]; const auditFile = path.join(auditDir, `audit-${date}.log`); const logLine = JSON.stringify(entry) + '\n'; await fs.appendFile(auditFile, logLine); } catch (error) { log.error('Failed to write audit log file:', error.message); } } /** * Sanitize request body for logging * @param {Object} body - Request body * @returns {Object} Sanitized body */ function sanitizeBody(body) { if (!body) return null; const sanitized = { ...body }; const sensitiveFields = ['password', 'passphrase', 'secret', 'token', 'key']; // Remove sensitive fields sensitiveFields.forEach(field => { if (sanitized[field]) { sanitized[field] = '[REDACTED]'; } }); // Truncate large fields Object.keys(sanitized).forEach(key => { if (typeof sanitized[key] === 'string' && sanitized[key].length > 100) { sanitized[key] = sanitized[key].substring(0, 100) + '...'; } }); return sanitized; } /** * Generate unique request ID * @returns {string} Request ID */ function generateRequestId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } /** * Get audit logs for a specific date range * @param {string} startDate - Start date (YYYY-MM-DD) * @param {string} endDate - End date (YYYY-MM-DD) * @returns {Array} Audit log entries */ export async function getAuditLogs(startDate, endDate) { try { const auditDir = path.resolve(process.cwd(), './logs/audit'); const logs = []; // Get all audit files in date range const files = await fs.readdir(auditDir); const auditFiles = files.filter(file => { if (!file.startsWith('audit-') || !file.endsWith('.log')) return false; const fileDate = file.replace('audit-', '').replace('.log', ''); return fileDate >= startDate && fileDate <= endDate; }); // Read and parse log files for (const file of auditFiles) { const filePath = path.join(auditDir, file); const content = await fs.readFile(filePath, 'utf8'); content.split('\n').forEach(line => { if (line.trim()) { try { logs.push(JSON.parse(line)); } catch (error) { log.warn('Failed to parse audit log line:', error.message); } } }); } return logs.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp)); } catch (error) { log.error('Failed to get audit logs:', error.message); return []; } } /** * Clean up old audit logs * @param {number} daysToKeep - Number of days to keep logs */ export async function cleanupAuditLogs(daysToKeep = 90) { try { const auditDir = path.resolve(process.cwd(), './logs/audit'); const files = await fs.readdir(auditDir); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysToKeep); for (const file of files) { if (!file.startsWith('audit-') || !file.endsWith('.log')) continue; const fileDate = file.replace('audit-', '').replace('.log', ''); const logDate = new Date(fileDate); if (logDate < cutoffDate) { const filePath = path.join(auditDir, file); await fs.unlink(filePath); log.info(`Deleted old audit log: ${file}`); } } } catch (error) { log.error('Failed to cleanup audit logs:', error.message); } }