Audit-Log + Brute-Force-Schutz + unlimited_budget + User-Delete-Fix

- Schema-Migration: ON DELETE SET NULL fuer incidents.created_by, magic_links.user_id,
  network_analyses.created_by (behebt 500er beim User-Loeschen). Neue Spalte
  licenses.unlimited_budget. Neue Tabellen portal_audit_log, portal_login_attempts.
- Audit-Log: alle CREATE/UPDATE/DELETE auf Org/User/Lizenz/Quelle + Login-Events
  werden mit before/after-Diff in portal_audit_log geschrieben.
- Brute-Force-Schutz: 5 Fehlversuche pro IP+Username/15min -> 429 mit Retry-After.
- Token-Budget: expliziter Schalter unlimited_budget pro Lizenz. UI zeigt ehrlich
  >100%-Verbrauch (kein Math.min mehr) und ungebremste Anzeige bei unlimited.
- Neuer Audit-Log Tab mit Filter (Aktion/Ressource/Admin/Zeitraum) und Pagination.
Dieser Commit ist enthalten in:
claude-dev
2026-05-02 20:16:03 +00:00
Ursprung 0da66fb585
Commit 4dc372814d
15 geänderte Dateien mit 1215 neuen und 151 gelöschten Zeilen

Datei anzeigen

@@ -777,3 +777,16 @@ tr:hover td {
.token-budget-bar.warning { background: #e67e22; }
.token-budget-bar.critical { background: #e74c3c; }
@media (max-width: 768px) { .token-stats-row { grid-template-columns: repeat(2, 1fr); } }
/* === Audit-Log === */
.audit-row { cursor: pointer; }
.audit-row:hover { background: var(--bg-hover, rgba(255,255,255,0.03)); }
.audit-detail-row td { background: var(--bg-tertiary, #0f172a); padding: 12px 16px; }
.audit-diff { width: 100%; border-collapse: collapse; font-size: 12px; }
.audit-diff th, .audit-diff td { padding: 4px 8px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.audit-diff th { font-weight: 600; color: var(--text-secondary); }
.audit-diff .diff-key { font-weight: 600; color: var(--text-secondary); width: 180px; word-break: break-word; }
.audit-diff .diff-old { color: #e74c3c; word-break: break-word; }
.audit-diff .diff-new { color: #2ecc71; word-break: break-word; }
.token-budget-bar.over-limit { background: repeating-linear-gradient(45deg, #c0392b, #c0392b 6px, #962d22 6px, #962d22 12px); }
input[type="date"].filter-select { padding: 6px 10px; }