feat(auth): Admin-Selbstanmeldung in die App (Login statt verlorenem Zugangslink)

Die App-Oberflaeche (/) ist bewusst nur per persoenlichem Zugangslink
erreichbar (kein SSO, damit Senioren keine Login-Maske sehen). Ein Admin, der
seinen Link verliert, kam bisher nicht mehr in die App-UI.

Neuer Endpunkt GET /api/admin/login: liegt hinter Authelia (nginx-Location
/api/admin/ mit auth_request + error_page-Redirect, Remote-User durchgereicht),
erkennt den SSO-Admin, mintet ein frisches Capability-Token, setzt es als
va_token-Cookie und leitet in die App (/). Damit meldet sich der Admin allein
mit seinem Authelia-Passwort an. Jeder Aufruf rotiert den Token (alte Links
dieses Admins werden ungueltig) - fuer eine Recovery-Funktion korrekt.

Die huebsche URL https://voice.jamulix.de/admin-login wird per nginx-Alias auf
diesen Endpunkt gelegt (nginx-Config liegt ausserhalb des Repos).

Doku: BEDIENUNGSANLEITUNG.md § 7.6 - Selbstanmeldung, Recovery per
ADMIN_API_KEY (SSO-unabhaengig) und Haertung (zweiter Admin in ADMIN_USERS,
Key im Passwortmanager).

Tests: Cookie wird gesetzt + 303 -> /, gesetzter Token authentifiziert,
Token-Rotation, Nicht-Admin/ohne Identitaet -> 403.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-26 12:37:14 +02:00
commit b4111c887e
3 changed files with 169 additions and 3 deletions

View file

@ -44,7 +44,7 @@ funktioniert trotzdem, die Ausgabe ist dann unformatiert.
6. [Einstellungen und Konfiguration](#6-einstellungen-und-konfiguration)
**Administration**
7. [Nutzerverwaltung und Authentifizierung](#7-nutzerverwaltung-und-authentifizierung) · [7.5 Admin-Web-Panel](#75-admin-web-panel)
7. [Nutzerverwaltung und Authentifizierung](#7-nutzerverwaltung-und-authentifizierung) · [7.5 Admin-Web-Panel](#75-admin-web-panel) · [7.6 Admin-Login & Recovery](#76-admin-zugang-ohne-persönlichen-link-selbstanmeldung--recovery)
8. [Gedächtnis und Erinnerungen](#8-gedächtnis-und-erinnerungen)
9. [Resilienz, Fallbacks und Metriken](#9-resilienz-fallbacks-und-metriken)
10. [Notfall-Erkennung und Eskalation](#10-notfall-erkennung-und-eskalation)
@ -1865,6 +1865,71 @@ Protokolliert werden u. a. `config_set` / `config_reset` (Laufzeit-Einstellungen
> Sichtbar im Log-Tab nur im **Dienst-Betrieb** (Journal). Im Vordergrund-Betrieb
> (`make run`) erscheinen die Audit-Zeilen im Terminal.
### 7.6 Admin-Zugang ohne persönlichen Link (Selbstanmeldung & Recovery)
> 🔧 Admin
Die App-Oberfläche (`/`) ist **nur per persönlichem Zugangslink** (`?k=<token>` → Cookie
`va_token`) erreichbar — bewusst ohne SSO, damit Senioren keine Login-Maske sehen. Wer
seinen Link verliert, kommt zunächst nicht in die App. Als **Admin** bist Du dadurch
**nicht ausgesperrt**: Du hast einen Selbst-Login und zwei unabhängige Recovery-Wege.
#### 7.6.1 Selbstanmeldung mit dem Admin-Passwort (empfohlen)
Rufe im Browser auf:
```
https://voice.jamulix.de/admin-login
```
Ablauf: Der Reverse-Proxy schickt Dich zu **Authelia** (`auth.jamulix.de`) → Du meldest
Dich mit Deinem **Admin-Passwort/2FA** an → das Gateway erkennt Dich als SSO-Admin
(`ADMIN_USERS`), setzt Dir ein frisches `va_token`-Cookie und leitet in die App (`/`).
**Kein Link nötig.** Bookmarke diese URL als „Admin-Login".
- Technisch dahinter steht der Endpunkt `GET /api/admin/login` (dieselbe Wirkung; die
hübsche URL `/admin-login` ist nur ein nginx-Alias darauf).
- **Jeder Aufruf rotiert Deinen Token** — alte persönliche Links *dieses Admin-Nutzers*
werden ungültig. Für eine Wiederherstellung korrekt und ein Sicherheitsplus.
#### 7.6.2 Recovery per `ADMIN_API_KEY` (SSO-unabhängig)
Funktioniert auch, wenn Authelia gerade nicht erreichbar ist — vom Server/localhost aus.
Den Key liest Du als root aus `/etc/voice-assistant/voice-assistant.env`.
```bash
# 1) Deine user_id finden:
curl -s $URL/api/admin/users -H "X-Admin-Key: $ADMIN_API_KEY" \
| jq -r '.[] | select(.external_id=="dschlueter") | .user_id'
USER_ID=<deine user_id>
# 2) Neuen Token ausstellen (wird EINMALIG zurückgegeben):
TOKEN=$(curl -s -X POST $URL/api/admin/users/$USER_ID/token \
-H "X-Admin-Key: $ADMIN_API_KEY" | jq -r '.token')
# 3) Daraus den Zugangslink bauen:
echo "https://voice.jamulix.de/?k=$TOKEN"
```
> Der `ADMIN_API_KEY` ist sehr mächtig (voller Admin-Zugriff). Nicht in Proxy-Logs
> geraten lassen (kein `?key=` in URLs verwenden, außer beim einmaligen Bootstrap über
> `GET /api/admin/request-headers?key=…`), und nach solchem Gebrauch rotieren.
#### 7.6.3 Härtung gegen Total-Aussperrung
Eine echte Vollsperre bräuchte: Link weg **und** Authelia-Konto weg **und**
`ADMIN_API_KEY` weg **und** kein SSH. Um auch das abzusichern:
- **Zweiter Admin:** einen weiteren Authelia-Namen in `ADMIN_USERS` aufnehmen (CSV),
damit ein einzelnes verlorenes Konto nicht alles blockiert:
```bash
# in /etc/voice-assistant/voice-assistant.env, dann Gateway neu starten:
ADMIN_USERS=dschlueter,<zweiter-admin>
```
- **`ADMIN_API_KEY` sichern:** im Passwortmanager hinterlegen (der Wert steht in
`/etc/voice-assistant/voice-assistant.env`).
---
## 8. Gedächtnis und Erinnerungen