import express from 'express'; import { AuditLog } from '../models/AuditLog.js'; import { AuditService } from '../services/auditService.js'; import { auth } from '../utils/auth.js'; import { metrics } from '../utils/metrics.js'; import { logger } from '../utils/logger.js'; const router = express.Router(); const auditService = new AuditService(); /** * Search audit logs */ router.get('/audit-logs', auth(), async (req, res) => { try { const { accountId, userId, category, action, resource, startDate, endDate, page = 1, limit = 50 } = req.query; const criteria = { accountId: accountId || req.user.accountId, userId, category, action, resource, startDate: startDate ? new Date(startDate) : undefined, endDate: endDate ? new Date(endDate) : undefined, page: parseInt(page), limit: parseInt(limit) }; const logs = await auditService.searchLogs(criteria); res.json({ success: true, data: logs, pagination: { page: parseInt(page), limit: parseInt(limit), hasMore: logs.length === parseInt(limit) } }); metrics.recordApiCall('audit.search', 'success'); } catch (error) { logger.error('Failed to search audit logs:', error); metrics.recordApiCall('audit.search', 'error'); res.status(500).json({ success: false, error: 'Failed to search audit logs' }); } }); /** * Get audit trail for a resource */ router.get('/audit-trail/:resourceType/:resourceId', auth(), async (req, res) => { try { const { resourceType, resourceId } = req.params; const trail = await auditService.getAuditTrail(resourceId, resourceType); res.json({ success: true, data: trail, count: trail.length }); metrics.recordApiCall('audit.trail', 'success'); } catch (error) { logger.error('Failed to get audit trail:', error); metrics.recordApiCall('audit.trail', 'error'); res.status(500).json({ success: false, error: 'Failed to get audit trail' }); } }); /** * Generate compliance report */ router.post('/audit-reports/compliance', auth(), async (req, res) => { try { const { startDate, endDate } = req.body; if (!startDate || !endDate) { return res.status(400).json({ success: false, error: 'Start date and end date are required' }); } const report = await auditService.generateComplianceReport( req.user.accountId, new Date(startDate), new Date(endDate) ); res.json({ success: true, data: report }); metrics.recordApiCall('audit.compliance_report', 'success'); } catch (error) { logger.error('Failed to generate compliance report:', error); metrics.recordApiCall('audit.compliance_report', 'error'); res.status(500).json({ success: false, error: 'Failed to generate compliance report' }); } }); /** * Detect anomalies */ router.get('/audit-anomalies', auth(), async (req, res) => { try { const { window = 3600000 } = req.query; // Default 1 hour const anomalies = await auditService.detectAnomalies( req.user.accountId, parseInt(window) ); res.json({ success: true, data: anomalies, count: anomalies.length, window: parseInt(window) }); metrics.recordApiCall('audit.anomalies', 'success'); } catch (error) { logger.error('Failed to detect anomalies:', error); metrics.recordApiCall('audit.anomalies', 'error'); res.status(500).json({ success: false, error: 'Failed to detect anomalies' }); } }); /** * Get activity summary */ router.get('/audit-summary', auth(), async (req, res) => { try { const { days = 7 } = req.query; const summary = await AuditLog.getActivitySummary( req.user.accountId, parseInt(days) ); res.json({ success: true, data: summary, period: { days: parseInt(days), startDate: new Date(Date.now() - parseInt(days) * 24 * 60 * 60 * 1000), endDate: new Date() } }); metrics.recordApiCall('audit.summary', 'success'); } catch (error) { logger.error('Failed to get activity summary:', error); metrics.recordApiCall('audit.summary', 'error'); res.status(500).json({ success: false, error: 'Failed to get activity summary' }); } }); /** * Get audit log by ID */ router.get('/audit-logs/:auditId', auth(), async (req, res) => { try { const { auditId } = req.params; const log = await AuditLog.findOne({ auditId }); if (!log) { return res.status(404).json({ success: false, error: 'Audit log not found' }); } // Check access permissions if (log.accountId !== req.user.accountId) { return res.status(403).json({ success: false, error: 'Access denied' }); } res.json({ success: true, data: log }); metrics.recordApiCall('audit.get', 'success'); } catch (error) { logger.error('Failed to get audit log:', error); metrics.recordApiCall('audit.get', 'error'); res.status(500).json({ success: false, error: 'Failed to get audit log' }); } }); /** * Export audit logs */ router.post('/audit-export', auth(), async (req, res) => { try { const { format = 'json', startDate, endDate, category, compress = false } = req.body; const criteria = { accountId: req.user.accountId, startDate: startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), endDate: endDate ? new Date(endDate) : new Date(), category }; const logs = await auditService.searchLogs(criteria); let exportData; let contentType; let filename = `audit_export_${Date.now()}`; switch (format) { case 'csv': exportData = convertToCSV(logs); contentType = 'text/csv'; filename += '.csv'; break; case 'json': default: exportData = JSON.stringify(logs, null, 2); contentType = 'application/json'; filename += '.json'; break; } if (compress) { // In production, implement compression filename += '.gz'; contentType = 'application/gzip'; } res.setHeader('Content-Type', contentType); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.send(exportData); metrics.recordApiCall('audit.export', 'success'); } catch (error) { logger.error('Failed to export audit logs:', error); metrics.recordApiCall('audit.export', 'error'); res.status(500).json({ success: false, error: 'Failed to export audit logs' }); } }); /** * Create manual audit log entry */ router.post('/audit-logs', auth(), async (req, res) => { try { const eventData = { ...req.body, accountId: req.user.accountId, actor: { type: 'user', id: req.user.id, ip: req.ip, userAgent: req.get('user-agent') } }; const auditLog = await auditService.logEvent(eventData); res.status(201).json({ success: true, data: auditLog }); metrics.recordApiCall('audit.create', 'success'); } catch (error) { logger.error('Failed to create audit log:', error); metrics.recordApiCall('audit.create', 'error'); res.status(500).json({ success: false, error: 'Failed to create audit log' }); } }); /** * Helper function to convert to CSV */ function convertToCSV(logs) { if (logs.length === 0) return ''; const headers = [ 'Timestamp', 'Audit ID', 'Action', 'Category', 'Resource', 'Resource ID', 'Actor Type', 'Actor ID', 'Result', 'IP Address' ]; const rows = logs.map(log => [ log.timestamp, log.auditId, log.action, log.category, log.resource || '', log.resourceId || '', log.actor.type, log.actor.id, log.result.status, log.actor.ip || '' ]); const csvContent = [ headers.join(','), ...rows.map(row => row.map(cell => typeof cell === 'string' && cell.includes(',') ? `"${cell}"` : cell ).join(',')) ].join('\n'); return csvContent; } export default router;