Die Anmeldung galt fest 7 Tage - und die Frist lief ab dem *Anmelden*, nicht
ab der letzten Nutzung. Wer die App taeglich benutzte, flog trotzdem
punktgenau nach einer Woche hinaus.
Beides ist jetzt geregelt:
- Einstellungen -> Anmeldung: 7 Tage bis 1 Jahr oder unbegrenzt. Gilt fuer
Website und App gemeinsam, weil beide dasselbe Token bekommen - auch nach
Single Sign-on.
- POST /auth/refresh schiebt die Frist beim Start nach vorn. Damit zaehlt die
eingestellte Dauer ab dem letzten Besuch, und eine geaenderte Einstellung
greift ohne Neuanmeldung.
"Unbegrenzt" heisst ein Token ganz ohne exp. Das ist bewusst folgenreich: Es
gibt keine Sperrliste, eine erteilte Anmeldung laesst sich nicht einzeln
zurueckziehen. Die Oberflaeche sagt das dazu und nennt den einzigen Ausweg
(JWT_SECRET aendern).
/auth/refresh nimmt bewusst nur JWTs an, keine langlebigen API-Token: aus
einem jederzeit widerrufbaren Zugang (Home Assistant) waere sonst ein
unwiderruflicher geworden.
Der Rueckfallwert aus der Umgebung steigt von 7 auf 30 Tage - er gilt nur noch,
solange nichts eingestellt ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Sitzungs-Token benannte den Benutzer ueber seinen Namen (sub = username).
Damit war der Name die Kennung der Sitzung - ein Umbenennen haette jede laufende
Anmeldung dieses Benutzers ungueltig gemacht, und deshalb war es gar nicht erst
vorgesehen. Beim Anmeldedienst geaenderte Namen liessen sich in Vorrania
folglich nirgends nachziehen.
Neue Token tragen die ID (sub = "<id>", zusaetzlich uid), und deps.py loest die
Sitzung darueber auf. Alles andere haengt ohnehin schon an der ID: Bewegungen,
Dashboards, API-Tokens. Der Name ist damit reine Anzeige.
Benutzer lassen sich jetzt unter "Benutzer" umbenennen - auch sich selbst, ohne
aus der Sitzung zu fliegen. Der Anmeldeweg selbst sucht weiterhin ueber den
Namen; das ist das Feld im Formular und soll so bleiben.
Bestandsschutz: Token ohne uid werden weiter ueber den Namen aufgeloest, damit
beim Update niemand abgemeldet wird. Der Zweig darf nach Ablauf der
Token-Lebensdauer weg. Das zusaetzliche uid unterscheidet alt von neu eindeutig -
ein Benutzer koennte schlicht "42" heissen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neben dem Passwort-Login gibt es jetzt einen zweiten, gleichberechtigten Weg:
OpenID Connect gegen einen selbst gehosteten Anmeldedienst. Ohne Konfiguration
aendert sich nichts - keine SSO-Knoepfe, Passwort-Login wie bisher.
Der Anmeldedienst ist NUR fuer die Anmeldung zustaendig. Danach stellt Vorrania
sein eigenes Token aus wie beim Passwort-Login, deshalb bleiben deps.py, die
Rollenpruefung und die langlebigen API-Tokens unveraendert.
Backend
- Anbieter kommen aus der Umgebung (OIDC_PROVIDERS + OIDC_<KUERZEL>_*). Ein
zweiter Anbieter ist damit reine Konfiguration, ohne Codeaenderung.
- Browser-Weg (vertraulicher Client) und App-Weg (oeffentlicher Client, PKCE)
enden beide im selben Token-Schema.
- state/nonce/PKCE-Verifier liegen in einer kurzlebigen Tabelle statt in einem
Cookie: Starlettes CORS-Schicht spiegelt bei allow_origins=["*"] zusammen mit
allow_credentials die Herkunft zurueck, sobald ein Request ein Cookie traegt.
Ohne Cookie entsteht diese Wechselwirkung erst gar nicht - und der Ablauf
bleibt mit der db-Fixture pruefbar.
- Zuordnung ueber die sub des Anbieters; die Erstverknuepfung laeuft ueber die
BESTAETIGTE E-Mail-Adresse. Rollen kommen nie aus dem Anbieter, und ein
gleichlautender preferred_username uebernimmt nie ein bestehendes Konto.
- users.password_hash darf jetzt leer sein (reine SSO-Konten). verify_password
faengt das ab, sonst antwortete der Login mit 500 statt 401.
Verknuepfen bestehender Konten
- Neue Seite "Mein Konto" (fuer alle Benutzer, nicht nur Admins): verknuepfen
und wieder loesen. Die Berechtigung stammt aus der laufenden Sitzung, es
braucht also weder eine hinterlegte Adresse noch Admin-Rechte.
- Der Start laeuft ueber POST, weil eine Seitennavigation das Bearer-Token
nicht mitschicken kann.
- Die letzte Anmeldemoeglichkeit eines Kontos laesst sich nicht loesen.
iOS
- AppAuth (erste Paketabhaengigkeit) mit PKCE im System-Browser, nie in einem
eingebetteten WebView, und ohne Client-Secret.
- offline_access nur im App-Weg: der Browser-Weg braucht kein Refresh-Token und
bekaeme sonst einen ueberfluessigen Zustimmungsdialog.
- Anmeldezustand je Serverprofil im Keychain; abgelaufene Token erneuert die App
still im Hintergrund. Gleichzeitige 401er teilen sich eine Erneuerung.
Deployment
- docker-compose reicht die ganze .env durch. Einzeln aufzuzaehlen ginge nur mit
fest verdrahteten Kuerzeln - genau das soll ein weiterer Anbieter nicht
erfordern.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Einkaufsliste war ueber /shopping-list und /shopping-list/groups schon
abrufbar, aber nur mit einem Login-JWT, das nach 7 Tagen ablaeuft - fuer HA
unpraktisch.
- Neue Tabelle api_tokens (Name, SHA-256-Hash, Ersteller, zuletzt genutzt).
Der Klartext wird nur einmal beim Anlegen zurueckgegeben.
- get_current_user akzeptiert jetzt JWT ODER API-Token im Authorization-Header;
bei Nutzung wird last_used_at fortgeschrieben.
- Endpunkte /api-tokens (Admin): auflisten, erzeugen, widerrufen.
- Einstellungsseite: Tokens verwalten inkl. einmaliger Anzeige und einem
aufklappbaren Konfigurationsbeispiel fuer Home Assistant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
passlib 1.7.4 vertraegt sich schlecht mit bcrypt 4.x und kann beim Hashen des
Admin-Passworts im Startup (ensure_first_admin) crashen -> Backend-Container in
Restart-Loop -> 502 in der Web-UI. hash_password/verify_password nutzen jetzt
bcrypt direkt (mit 72-Byte-Grenze). passlib aus requirements entfernt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>