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:
@@ -2,6 +2,7 @@
|
||||
const db = require('../../db/db');
|
||||
const { stmts } = require('./_shared');
|
||||
const { awardXP, awardCoins } = require('./service');
|
||||
const { audit } = require('../../utils/audit');
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
Gamification — admin handlers
|
||||
@@ -22,6 +23,9 @@ function adminAward(req, res) {
|
||||
if (!user) return res.status(404).json({ error: 'User not found' });
|
||||
if (xpNum > 0) awardXP(userId, xpNum, reason || 'Admin award');
|
||||
if (coinsNum > 0) awardCoins(userId, coinsNum, reason || 'Admin award');
|
||||
if (xpNum > 0 || coinsNum > 0) {
|
||||
audit(req, 'gam.award', `user:${userId}`, `+${xpNum} XP, +${coinsNum} монет — ${reason || 'Admin award'}`);
|
||||
}
|
||||
const updated = stmts.getUserGamInfo.get(userId);
|
||||
res.json({ ok: true, ...updated });
|
||||
}
|
||||
@@ -40,6 +44,7 @@ function adminReset(req, res) {
|
||||
const { userId } = req.body;
|
||||
if (!userId) return res.status(400).json({ error: 'userId required' });
|
||||
_resetTx(userId);
|
||||
audit(req, 'gam.reset', `user:${userId}`, 'сброс XP/монет/ачивок/истории');
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user