# Analyse: Benutzerverwaltung LBM

Bestandsaufnahme und inkrementelles Konzept. Stand: Analyse des Ist-Codes;
Umsetzung noch offen.

## Ist-Zustand

### Benutzer anlegen — existiert nicht im Code

Es gibt **keine** Benutzerverwaltung: weder Anlegen noch Bearbeiten noch
Deaktivieren/Löschen ist implementiert.

- `public/settings/index.php` ist **nicht** die Benutzerverwaltung — die Seite
  verwaltet nur Lookup-Werte (Kategorie, Massnahme, Status, Externe Organisation,
  Gemeinde). Kein Bezug zu `tabUser`.
- Im gesamten Anwendungscode gibt es kein `INSERT INTO tabUser`, kein
  `createUser`/`addUser`, kein Setzen von `usrDeleted = 1`.
- `App\Repository\UserRepository` hat **nur lesende Methoden plus
  `updatePassword`**: `findByLogin`, `findByEmail`, `findById`, `findActive`,
  `findActiveSortedByName`, `updatePassword(int $id, string $hash)`. Kein
  `insert`/`create`/`update`/`softDelete`.
- **Felder** von `tabUser` (`db/schema.sql`): `usrID`, `usrLogin`, `usrPass`,
  `usrFirstName`, `usrLastName`, `usrEmail`, `usrNickName`, `usrDeleted`
  (Default 0). `UNIQUE KEY usrLogin`. **Keine Rollen-/Level-/Admin-Spalte.**
- **Passwort-Setzung:** Der einzige Ort, an dem ein Passwort-Hash entsteht, ist
  der Reset-Flow (`public/login/reset.php`, `password_hash(..., PASSWORD_DEFAULT)`).
  Neue Benutzer müssen aktuell direkt in der DB angelegt werden.

### Rechte/Rollen — nicht modelliert

- `SUPERUSERS` ist eine reine `.env`-Liste von IDs (Default `1,2`), gelesen über
  `config('app.superusers')`.
- Sie wird an **genau einer Stelle** ausgewertet: dem Wartungsmodus
  (`header.php` — Nicht-Superuser landen im Maintenance-Modus auf
  `maintenance.php`). Ausserhalb des Wartungsmodus hat die Liste keine Wirkung.
- **Keine Session-Rollen.** Beim Login werden nur `loggedin`, `usrName`,
  `usrLogin`, `usrID` gesetzt — kein Rollen-/Admin-Flag.
- Einzige serverseitige Zugriffskontrolle: „eingeloggt ja/nein" (`header.php`)
  plus zentraler CSRF-Schutz (`\App\Security\Csrf::guard()`). Keine feingranulare
  Autorisierung.

### Passwort ändern im eingeloggten Zustand — gibt es nicht

- Keine Profil-/Kontoseite. Das Benutzer-Menü (`partials/navbar.php`) enthält nur
  Neuigkeiten, Doku-Archiv, Einstellungen, Abmelden.
- Vorhanden ist nur der **ausgeloggte Passwort-vergessen-Flow**:
  - `public/login/forgot.php`: E-Mail-Eingabe, `findByEmail`, Rate-Limit
    (max. 5/Stunde), Token als `bin2hex(random_bytes(32))`, in der DB nur der
    SHA-256-Hash, Mailversand des Links, neutrale Antwort (keine Konto-Enumeration).
  - `public/login/reset.php`: Token-Validierung, Passwort-Policy (min. 12 Zeichen,
    1 Zahl, 1 Sonderzeichen), Hash setzen via `updatePassword`, Token verbrauchen
    und offene Links entwerten.
  - `App\Repository\PasswordResetRepository` + Tabelle `tabPasswordReset`.
- Ein eingeloggter Nutzer kann sein Passwort also nur über den Umweg
  „Ausloggen → Passwort vergessen → E-Mail" ändern. Eine direkte Änderung mit
  Eingabe des **alten** Passworts fehlt.

## Lücken

1. Keine Self-Service-Passwortänderung im Login-Zustand (mit Alt-Passwort-Prüfung).
2. Keine Benutzerverwaltung (Anlegen/Bearbeiten/Deaktivieren) — Benutzer entstehen
   nur per manuellem DB-Eingriff.
3. Keine echten Rollen — nur die wirkungsarme `SUPERUSERS`-`.env`-Liste; keine
   DB-Spalte, kein Session-Flag, keine serverseitige Admin-Autorisierung.
4. Kein Einladungs-/Onboarding-Flow (Anlegen ohne manuell gesetztes Passwort).

## Vorhandene Bausteine (Basis, reversibel/low-risk)

- CSRF zentral (`Csrf::guard()`; Formularfeld `_csrf` = Cookie-Wert).
- `password_hash`/`password_verify`-Muster (authenticate/reset).
- Passwort-Policy und Rate-Limit aus dem Reset-Flow wiederverwendbar.
- `UserRepository::updatePassword` und `findById` vorhanden.

## Konzept (inkrementell, additiv, reversibel)

### Etappe A (Priorität 1): Passwort selbst ändern im eingeloggten Zustand

Kleinster, isolierter Baustein — **keine Schema-Änderung**.

- Neue Seite `public/settings/password.php` (oder `public/account/password.php`),
  bindet `header.php` ein → automatisch Login- + CSRF-Schutz. Formular: aktuelles
  Passwort, neues Passwort, Wiederholung; Feld `_csrf` wie im Reset.
- Serverlogik:
  1. `UserRepository::findById($_SESSION['usrID'])` → `usrPass` holen.
  2. `password_verify(altesPasswort, usrPass)` — bei Fehlschlag Abbruch.
  3. Gleiche Policy wie im Reset (min. 12 Zeichen, Zahl, Sonderzeichen),
     idealerweise als gemeinsame Validierungsfunktion (DRY mit `reset.php`).
  4. `updatePassword(usrID, password_hash($neu, PASSWORD_DEFAULT))`.
  5. Optional `PasswordResetRepository::invalidateForUser()`, damit offene
     Reset-Links entwertet werden.
- Sicherheit: Alt-Passwort-Pflicht; leichtes Rate-Limit/Verzögerung denkbar;
  `session_regenerate_id()` nach Änderung erwägen.
- Menüpunkt „Passwort ändern" in `partials/navbar.php` ergänzen.

### Etappe B (Priorität 2): Rollen/Rechte sauber

- **B1 – DB & Session:** Spalte `usrIsAdmin tinyint(1) NOT NULL DEFAULT 0` (oder
  `usrRole` ENUM) in `tabUser`. Beim Login `$_SESSION['usrIsAdmin']` setzen. Die
  bestehende `SUPERUSERS`-Liste dient als Migrationsquelle (IDs 1,2 → Flag=1) und
  wird danach als Rechtequelle abgelöst (Maintenance-Check auf das neue Flag).
- **B2 – zentraler Guard:** Helper `require_admin()` (Abbruch mit 403, analog
  `Csrf::guard()`), der jede künftige Admin-Seite serverseitig schützt — nicht nur
  UI-Verstecken.

### Etappe C (Priorität 3): Benutzerverwaltung + Einladung

Setzt B voraus (Admin-Guard).

- `UserRepository` erweitern: `insert()` (Login/Namen/E-Mail, `usrPass = NULL`,
  `usrDeleted = 0`), `update()`, `softDelete()` (`usrDeleted = 1`),
  `findAllIncludingDeleted()`.
- Admin-Seite `public/settings/users.php` (hinter `require_admin()`): Liste,
  Anlegen, Bearbeiten, Deaktivieren.
- **Einladung statt manuellem Passwort:** Beim Anlegen kein Passwort setzen,
  sondern den Reset-Mechanismus wiederverwenden — `PasswordResetRepository::create()`
  mit längerer Gültigkeit und Versand eines „Konto einrichten"-Links. So gibt es
  nie ein administrativ vergebenes Klartext-Passwort.

### Reihenfolge

A (sofort, kein Schema) → B1 → B2 → C. A und B sind unabhängig; C hängt an B.
Alle Schritte additiv und einzeln zurücknehmbar.
