Admin kann Nutzer-Konto übernehmen (Impersonation) mit Rückwechsel

- sessions.impersonator_id: Admin-Session behält Token, schaltet user_id
  aufs Ziel, Admin bleibt in impersonator_id gespeichert (Stack, keine
  Verschachtelung). uid()/Config/Dateien laufen als Ziel-Nutzer.
- Admin sieht in /admin pro Nutzer "Anmelden als"; Bestätigungsdialog.
- Dauerhaftes amber Banner im Header mit "Zurück zum Admin" (POST, kein JS
  nötig) erscheint auf jeder Seite während Impersonation.
- requireAdmin verweigert während Impersonation -> keine Admin-Aktionen als
  fremder Nutzer; Stop-Route prüft impersonator_id (kein Escalation-Pfad für
  Normalnutzer). Selbst-Imitation blockiert.
- audit_log-Tabelle protokolliert Start/Stop persistent; Admin-Seite zeigt
  Audit-Liste (/admin/audit/impersonations).
- Migration: idempotentes ALTER ADD COLUMN impersonator_id fuer Bestand.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-14 02:17:50 +02:00
co-authored by Claude
parent fd1db3970f
commit e0733666ae
4 changed files with 225 additions and 6 deletions
+6
View File
@@ -79,6 +79,12 @@ async function runMigration({ db, dbAll, dbGet, dbRun }) {
)
`);
await exec('CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)');
// Impersonation support: the admin's session keeps its token but switches
// user_id to the target, storing the original admin in impersonator_id so the
// admin can switch back. Added after the fact for existing installs.
if (!(await hasColumn('sessions', 'impersonator_id'))) {
await exec('ALTER TABLE sessions ADD COLUMN impersonator_id INTEGER');
}
// 2. Ensure admin user (idempotent) -----------------------------------
let admin = await dbGet('SELECT id, password_hash FROM users WHERE username = ?', [ADMIN_USERNAME]);