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
+26
View File
@@ -1076,6 +1076,9 @@
<button class="admin-nav-item" data-tab="audit" onclick="switchTab(this)">
<i data-lucide="scroll-text" style="width:15px;height:15px"></i> Аудит-лог
</button>
<button class="admin-nav-item" data-tab="security" onclick="switchTab(this)">
<i data-lucide="shield-alert" style="width:15px;height:15px"></i> Безопасность
</button>
<button class="admin-nav-item" data-tab="errors" onclick="switchTab(this)">
<i data-lucide="bug" style="width:15px;height:15px"></i> Ошибки
</button>
@@ -1680,6 +1683,29 @@
<div id="audit-list"></div>
</div>
<!-- ── Безопасность / журнал событий ── -->
<div class="tab-pane" id="tab-security">
<div class="section-title" style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
Журнал событий безопасности
<button class="adm-btn adm-btn-danger adm-btn-small" onclick="clearSecurityLog()">Очистить</button>
</div>
<p class="perm-desc" style="margin:-8px 0 16px;max-width:760px">
Входы и неудачные попытки, отказы доступа (роль/разрешение) и превышения лимита запросов.
Записываются IP и e-mail попытки — даже для неавторизованных.
</p>
<div class="sl-filter-row">
<select class="sl-filter-select" id="sec-cat-filter" onchange="loadSecurityLog()">
<option value="">Все категории</option>
<option value="auth">Вход / аккаунт</option>
<option value="access_denied">Отказы доступа</option>
<option value="rate_limit">Превышение лимита</option>
</select>
<input class="t-input" id="sec-search" type="text" placeholder="Поиск: email, IP, маршрут…" oninput="secSearchDebounce()" style="min-width:220px" />
<span class="sl-count" id="sec-count"></span>
</div>
<div id="security-list"></div>
</div>
<!-- ── Ошибки ── -->
<div class="tab-pane" id="tab-errors">
<div class="section-title" style="display:flex;align-items:center;justify-content:space-between">