const AuditLog = require('../models/AuditLog'); // Map resource name → Mongoose model (lazy-loaded to avoid circular deps) const getModel = (resource) => { const map = { User: () => require('../models/User'), Config: () => require('../models/Config'), Admin: () => require('../models/Admin') }; return map[resource] ? map[resource]() : null; }; // Remove internal Mongoose / sensitive fields for clean snapshots const stripInternals = (obj) => { if (!obj) return null; const { __v, _id, createdAt, updatedAt, deleted, deletedAt, deletedBy, password, passwordHash, ...clean } = obj; return clean; }; // Compute field-level diff between two plain objects const computeDiff = (before, after) => { if (!before || !after) return null; const diff = {}; const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]); for (const key of allKeys) { if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) { diff[key] = { before: before[key], after: after[key] }; } } return Object.keys(diff).length > 0 ? diff : null; }; /** * Middleware to automatically log admin actions with full before/after state. * Usage: router.post('/users', authenticateToken, auditLog('CREATE', 'User'), createUser); */ const auditLog = (action, resource) => { return async (req, res, next) => { const startTime = Date.now(); // Capture before-state for UPDATE actions let beforeState = null; if (action === 'UPDATE' && req.params?.id) { try { const Model = getModel(resource); if (Model) { const doc = await Model.findById(req.params.id).lean(); beforeState = stripInternals(doc); } } catch (_) { /* non-fatal */ } } const originalJson = res.json.bind(res); res.json = function(data) { const duration = Date.now() - startTime; const statusCode = res.statusCode; setImmediate(async () => { try { const isSuccess = data.success !== false && statusCode < 400; const logData = { action, resource, adminId: req.user?.id || null, adminUsername: req.user?.username || 'unknown', ipAddress: req.ip || req.connection?.remoteAddress, userAgent: req.get('user-agent'), requestMethod: req.method, requestPath: req.originalUrl?.split('?')[0], statusCode, duration, success: isSuccess }; // Resource ID if (data.data?._id) logData.resourceId = data.data._id; else if (req.params?.id) logData.resourceId = req.params.id; // Resource name if (data.data?.name) logData.resourceName = data.data.name; else if (data.data?.username) logData.resourceName = data.data.username; // Before / after diff for UPDATE if (action === 'UPDATE') { const raw = data.data ? (data.data.toObject ? data.data.toObject() : data.data) : null; const afterState = stripInternals(raw); logData.before = beforeState; logData.after = afterState; logData.changes = computeDiff(beforeState, afterState); } // IMPORT: store import statistics in metadata if (action === 'IMPORT' && data.results) { logData.metadata = { imported: data.results.imported, skipped: data.results.skipped, errors: data.results.errors?.length || 0 }; logData.resourceName = `Import (${data.results.imported} importiert, ${data.results.skipped} übersprungen)`; } // EXPORT: store format if (action === 'EXPORT') { const fmt = req.query.format?.toUpperCase() || 'JSON'; logData.metadata = { format: fmt }; logData.resourceName = `Export (${fmt})`; } // BULK operations: store count + affected fields if (action === 'BULK_UPDATE' || action === 'BULK_DELETE') { const count = Array.isArray(req.body?.ids) ? req.body.ids.length : (data.data?.matched || 0); logData.metadata = { count, ...(req.body?.updates ? { fields: Object.keys(req.body.updates) } : {}) }; logData.resourceName = `${action === 'BULK_DELETE' ? 'Massen-Löschung' : 'Massen-Update'} (${count} Einträge)`; } // Error message if (!isSuccess && data.message) { logData.errorMessage = data.message; } await AuditLog.log(logData); } catch (error) { const logger = require('../utils/logger'); logger.error('Audit logging error:', error); } }); return originalJson(data); }; next(); }; }; /** * Log authentication attempts (success and failure) */ const auditAuth = async (req, isSuccess, username, errorMessage = null) => { try { await AuditLog.log({ action: isSuccess ? 'LOGIN' : 'LOGIN_FAILED', resource: 'Admin', adminUsername: username, ipAddress: req.ip || req.connection?.remoteAddress, userAgent: req.get('user-agent'), requestMethod: req.method, requestPath: req.originalUrl?.split('?')[0], statusCode: isSuccess ? 200 : 401, success: isSuccess, errorMessage }); } catch (error) { const logger = require('../utils/logger'); logger.error('Auth audit logging error:', error); } }; module.exports = { auditLog, auditAuth };