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:
@@ -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' });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* Simple in-memory rate limiter — no external dependency needed */
|
||||
const { logRateLimit } = require('../utils/securityLog');
|
||||
|
||||
// Clean stale entries every 5 minutes across all stores
|
||||
const _allStores = new Set();
|
||||
@@ -35,6 +36,8 @@ module.exports = function rateLimit({ windowMs = 60_000, max = 10, message = 'To
|
||||
store.set(key, entry);
|
||||
|
||||
if (entry.count > max) {
|
||||
// Log once per window (on first crossing) to avoid spamming the security log.
|
||||
if (entry.count === max + 1) logRateLimit(req, `лимит ${max}/${Math.round(windowMs / 1000)}с`);
|
||||
const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
|
||||
res.set('Retry-After', retryAfter);
|
||||
return res.status(429).json({ error: message });
|
||||
|
||||
Reference in New Issue
Block a user