Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | /** * Routes Audit — Consultation et export des logs d'audit. * * Endpoints : * GET /audit/logs — Lister les logs (pagination + filtres) * GET /audit/logs/export — Exporter en CSV * GET /audit/stats — Statistiques par categorie * DELETE /audit/logs — Purger les logs anciens * * @module routes/audit */ const express = require('express'); const { pool: db } = require('../db'); const { requireAuth } = require('../middleware/auth'); const { requireFeature } = require('../middleware/planLimits'); const { requireGroupContext, requireGroupPermission } = require('../middleware/groupContext'); const router = express.Router(); router.use(requireAuth); router.use(requireGroupContext()); // Categories valides pour le filtre const VALID_CATEGORIES = new Set([ 'auth', 'server', 'docker', 'ssh', 'ssh_key', 'terminal', 'chat', 'settings', 'backup', 'alert', 'file', 'cron', 'webhook', 'ssl', 'uptime', 'status_page', 'export', 'general', ]); // ─── GET /audit/logs ──────────────────────────────────────────────────────── router.get('/logs', requireGroupPermission('audit', 'read'), async (req, res, next) => { try { const page = Math.max(1, Number.parseInt(req.query.page) || 1); const limit = Math.min(100, Math.max(1, Number.parseInt(req.query.limit) || 50)); const offset = (page - 1) * limit; const category = req.query.category; const search = req.query.search; const range = req.query.range || '7d'; const intervals = { '24h': '24 hours', '7d': '7 days', '30d': '30 days', '90d': '90 days' }; const interval = intervals[range] || '7 days'; let whereClause = `WHERE al.user_id = $1 AND al.created_at > NOW() - INTERVAL '${interval}'`; const params = [req.user.id]; let paramIndex = 2; // En mode groupe, filtrer les logs d'audit par group_id if (req.access) { whereClause += ` AND al.group_id = $${paramIndex}`; params.push(req.access.groupId); paramIndex++; } if (category && VALID_CATEGORIES.has(category)) { whereClause += ` AND al.category = $${paramIndex}`; params.push(category); paramIndex++; } if (search && typeof search === 'string' && search.length <= 100) { whereClause += ` AND (al.action ILIKE $${paramIndex} OR al.target ILIKE $${paramIndex} OR al.ip ILIKE $${paramIndex})`; params.push(`%${search}%`); paramIndex++; } // Count total const countResult = await db.query( `SELECT COUNT(*) FROM audit_logs al ${whereClause}`, params, ); const total = Number.parseInt(countResult.rows[0].count); // Fetch logs const { rows } = await db.query( `SELECT al.id, al.action, al.category, al.target, al.details, al.ip, al.created_at FROM audit_logs al ${whereClause} ORDER BY al.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [...params, limit, offset], ); res.json({ logs: rows, pagination: { page, limit, total, total_pages: Math.ceil(total / limit) }, }); } catch (err) { next(err); } }); // ─── GET /audit/logs/export — Export CSV ──────────────────────────────────── router.get('/logs/export', requireGroupPermission('audit', 'read'), requireFeature('export_csv_audit'), async (req, res, next) => { try { const range = req.query.range || '30d'; const intervals = { '7d': '7 days', '30d': '30 days', '90d': '90 days' }; const interval = intervals[range] || '30 days'; const { rows } = await db.query( `SELECT al.action, al.category, al.target, al.ip, al.created_at, al.details->>'method' AS method, al.details->>'path' AS path, al.details->>'status' AS status FROM audit_logs al WHERE al.user_id = $1 AND al.created_at > NOW() - INTERVAL '${interval}' ORDER BY al.created_at DESC LIMIT 10000`, [req.user.id], ); // Generer le CSV const header = 'Date,Action,Categorie,Cible,Methode,Chemin,Status,IP\n'; const csvRows = rows.map((r) => { const date = new Date(r.created_at).toISOString(); // Echapper les guillemets dans les champs CSV const escape = (v) => `"${(v || '').replaceAll('"', '""')}"`; return [date, escape(r.action), escape(r.category), escape(r.target), escape(r.method), escape(r.path), r.status || '', escape(r.ip)].join(','); }); res.setHeader('Content-Type', 'text/csv; charset=utf-8'); res.setHeader('Content-Disposition', `attachment; filename="audit-logs-${range}.csv"`); res.send(header + csvRows.join('\n')); } catch (err) { next(err); } }); // ─── GET /audit/stats — Statistiques ──────────────────────────────────────── router.get('/stats', requireGroupPermission('audit', 'read'), async (req, res, next) => { try { const { rows } = await db.query( `SELECT category, COUNT(*) AS count FROM audit_logs WHERE user_id = $1 AND created_at > NOW() - INTERVAL '30 days' GROUP BY category ORDER BY count DESC`, [req.user.id], ); const { rows: recent } = await db.query( `SELECT COUNT(*) AS count FROM audit_logs WHERE user_id = $1 AND created_at > NOW() - INTERVAL '24 hours'`, [req.user.id], ); res.json({ by_category: rows, last_24h: Number.parseInt(recent[0].count), }); } catch (err) { next(err); } }); // ─── DELETE /audit/logs — Purger les anciens logs ─────────────────────────── router.delete('/logs', requireGroupPermission('audit', 'read'), async (req, res, next) => { try { const days = Math.max(7, Math.min(Number.parseInt(req.query.days) || 90, 365)); const result = await db.query( `DELETE FROM audit_logs WHERE user_id = $1 AND created_at < NOW() - INTERVAL '${days} days'`, [req.user.id], ); res.json({ message: `${result.rowCount} logs supprimes (> ${days} jours).` }); } catch (err) { next(err); } }); module.exports = router; |