feat(admin): журнал событий безопасности (Tier 1-2) + аудит чувствительных действий (Tier 3)

- security_events (миграция 047) + utils/securityLog.js (defensive, lazy stmt)
- Tier 1: login.success/fail, register, password.change в authController
- Tier 2: 403 (роль/разрешение) в middleware/auth, rate_limited в rateLimit
- Tier 3: audit() на выдачу доступа (access), начисление/сброс XP (gam), модерацию аватаров
- API GET/DELETE /api/admin/security-log (фильтр по категории + поиск, прунинг по дням)
- Frontend: вкладка «Безопасность» в admin.html + loadSecurityLog, расширены ACTION_LABELS

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-01 15:28:21 +03:00
parent 30626e0928
commit fe122b7681
12 changed files with 262 additions and 2 deletions
+6 -1
View File
@@ -1,6 +1,7 @@
const jwt = require('jsonwebtoken');
const db = require('../db/db');
const registry = require('../permissions/registry');
const { logDenied } = require('../utils/securityLog');
/* ── Default values for role_permissions — sourced from central registry ── */
const PERM_DEFAULTS = registry.buildDefaultsMap();
@@ -34,6 +35,7 @@ function authMiddleware(req, res, next) {
function requireRole(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user?.role)) {
if (req.user) logDenied(req, 'forbidden', `роль ${req.user.role}; требуется ${roles.join('/')}`);
return res.status(403).json({ error: 'Forbidden' });
}
next();
@@ -53,7 +55,9 @@ function requirePermission(key) {
'SELECT enabled FROM user_permissions WHERE user_id = ? AND permission = ?'
).get(uid, key);
if (userRow !== undefined) {
return userRow.enabled === 1 ? next() : res.status(403).json({ error: 'Permission denied' });
if (userRow.enabled === 1) return next();
logDenied(req, 'perm_denied', key);
return res.status(403).json({ error: 'Permission denied' });
}
// 2. Role-level
@@ -62,6 +66,7 @@ function requirePermission(key) {
).get(role, key);
const enabled = roleRow !== undefined ? roleRow.enabled === 1 : (PERM_DEFAULTS[role]?.[key] ?? false);
if (enabled) return next();
logDenied(req, 'perm_denied', key);
return res.status(403).json({ error: 'Permission denied' });
};
}