Android und Desktop melden sich selbst bei Authelia an. Mit dem geprüften Ergebnis bekommen sie von diora den Zugang des verknüpften Kontos, so wie bisher nach der Anmeldung mit Passwort. Den Bücher-Schlüssel öffnen sie selbst, der Server sieht ihn nicht. Details: docs/accounts.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
6 KiB
Markdown
91 lines
6 KiB
Markdown
# Anmeldung und Bücher-Schlüssel
|
|
|
|
## Zwei Wege hinein
|
|
|
|
**Benutzername und Passwort** (`accounts/views.py` `login_view`): Das Login-Formular leitet im
|
|
Browser aus dem Passwort den Bücher-Schlüssel ab (PBKDF2-SHA256, Salt `diora:<username>`,
|
|
200 000 Runden) und legt ihn bis zur nächsten Seite in `localStorage['diora_pending_enc_key']`
|
|
ab. Die App-Seite übernimmt ihn als `diora_enc_key_<userId>` (`32-crypto.js`, `99-init.js`).
|
|
|
|
**Authelia (OpenID Connect)** (`accounts/oidc.py`, `accounts/oidc_views.py`): als zusätzlicher
|
|
Weg, die Anmeldung mit Passwort bleibt. Der Ablauf ist Authorization Code mit PKCE (S256) und
|
|
einem vertraulichen Client (`client_secret_basic`). Das id_token wird hier geprüft (RS256 gegen
|
|
die JWKS des Anbieters, `iss`, `aud`, `exp`, `nonce`), dafür reicht `cryptography`.
|
|
|
|
- Ein Konto wird **einmal verknüpft**: in den Einstellungen „Mit Authelia verknüpfen“, während
|
|
man normal angemeldet ist (`/accounts/oidc/start/?link=1`). Gespeichert wird `OidcIdentity`
|
|
(`issuer`, `sub`; Name und Mail nur zur Anzeige). Danach führt „Mit Authelia anmelden“ auf der
|
|
Login-Seite in dieses Konto.
|
|
- Eine nicht verknüpfte Authelia-Identität wird abgewiesen. Es entstehen **keine neuen
|
|
Konten** über Authelia, und es wird auch nicht über Name oder Mail automatisch verknüpft.
|
|
- Wer hinein darf, regelt Authelia (Policy `mitglieder` = Gruppen `admins` oder `freunde`).
|
|
- Einstellungen per Umgebung: `DIORA_OIDC_ISSUER`, `DIORA_OIDC_CLIENT_ID`,
|
|
`DIORA_OIDC_CLIENT_SECRET`, `DIORA_OIDC_REDIRECT_URI` (fest, weil hinter dem Proxy kein
|
|
https erkannt wird), optional `DIORA_OIDC_PROVIDER_NAME`. Fehlt eins der ersten drei, gibt es
|
|
keinen Knopf. Auf prod stehen sie in `~/docker-compose/diora-prod/.env` (Strato), das
|
|
Gegenstück liegt auf Hetzner in `/opt/authelia/oidc-clients.env`.
|
|
- Redirect-URI im Authelia-Client: `https://diora.creamfresh.xyz/accounts/oidc/callback/`.
|
|
|
|
## Der Schlüssel nach einer Authelia-Anmeldung
|
|
|
|
### Normalfall: über Authelia, ohne Nachfrage
|
|
|
|
Authelia hält je Nutzer ein geheimes Attribut `diora_key` (32 Byte, base64). Es geht nur an den
|
|
öffentlichen Client `diora-app` (`DIORA_OIDC_APP_CLIENT_ID`, PKCE, Scope `diora_key`) und nur
|
|
über userinfo, nicht im id_token. Nach jedem Authelia-Login (und nach dem Verknüpfen) führt
|
|
diora auf `/accounts/oidc/key/` (`templates/accounts/oidc_key.html`). Die Seite meldet sich
|
|
**im Browser** noch einmal mit `prompt=none` als `diora-app` an, unsichtbar, weil die
|
|
Authelia-Sitzung noch besteht, und holt per CORS Token und userinfo. Daraus leitet sie mit
|
|
HKDF-SHA256 (Salt = `sub`, info `diora authelia key v1`) einen AES-GCM-Schlüssel ab:
|
|
|
|
- Hat das Gerät den Bücher-Schlüssel (und passt er zu einem Buch), wird er damit verschlüsselt
|
|
und im Vault `authelia-key` abgelegt, bei jedem Authelia-Login neu. So heilt ein geänderter
|
|
`diora_key` von selbst.
|
|
- Hat es keinen, öffnet sie den Vault-Eintrag damit und legt den Schlüssel ab.
|
|
- Es zählt nur das Authelia-Konto, das mit diesem diora-Konto verknüpft ist (`sub`).
|
|
|
|
Der diora-Server sieht `diora_key` nie. **Grenze, ehrlich:** Wer Authelia (Hetzner-Root) oder
|
|
dein Authelia-Konto (Passwort, Mail-Reset) übernimmt, kommt damit an die Bücher. Wer Strato
|
|
übernimmt, kann im Browser ohnehin manipuliertes JS ausliefern. Geschützt ist gegen geleakte
|
|
Daten **einer** Seite (diora-DB/Backup ohne Authelia-Nutzerdatei oder umgekehrt). Geht
|
|
`diora_key` verloren, sind die Bücher nicht weg: Der Schlüssel kommt weiter aus dem Passwort.
|
|
Entschieden 2026-11-01 vom Nutzer (Komfort vor stärkerer Trennung).
|
|
|
|
### Notweg: Code oder Passwort
|
|
|
|
Ohne Passwort gibt es keinen Schlüssel. Deshalb erzeugt `getOrCreateEncKey()` **nie mehr
|
|
selbst einen neuen Schlüssel**: Früher machte es einen zufälligen, wenn keiner da war, und damit
|
|
waren die vorhandenen Bücher auf diesem Gerät verloren. Jetzt fragt es `dioraUnlock()`
|
|
(`static/js/app/33-unlock.js`). Es gibt zwei Wege:
|
|
|
|
1. **Von einem entsperrten Gerät übergeben** (`accounts/keyshare.py`, Modell `KeyShare`). Das
|
|
neue Gerät schickt einen ECDH-P-256-Public-Key und zeigt einen 6-stelligen Code, der aus
|
|
diesem Key berechnet wird. Entsperrte Geräte fragen alle 15 s
|
|
`/accounts/keyshare/pending/` ab und zeigen denselben Code mit „Freigeben/Ablehnen“. Die
|
|
Freigabe verschlüsselt den Bücher-Schlüssel mit AES-GCM unter dem gemeinsamen Geheimnis
|
|
(HKDF-SHA256, info `diora keyshare v1`). Der Server sieht nur Public Keys und Chiffrat und
|
|
gibt die Antwort genau einmal heraus. Anträge laufen nach 10 min ab. Angeboten werden nur
|
|
solche, deren Gerät in den letzten 20 s nachgefragt hat, und je Konto höchstens 5, die
|
|
ältesten weichen.
|
|
2. **Das diora-Passwort einmal eingeben**: dieselbe Ableitung wie beim Login.
|
|
|
|
In beiden Fällen wird der Schlüssel an einem Buch geprüft, bevor er gespeichert wird. Mit
|
|
„Später“ bleiben Bücher gesperrt, und die Bibliothek zeigt einen Knopf „Entsperren“. Die
|
|
Einstellungsseite erzeugt ebenfalls keinen Schlüssel mehr.
|
|
|
|
## Apps
|
|
|
|
Android und Desktop melden sich bisher nur mit Passwort an (eigene Login-Seiten in
|
|
`diora-android/web-native/login.html` und `diora-desktop/src/web/login.js`, die einen
|
|
API-Token holen). Mit Authelia melden sie sich selbst als öffentlicher Client `diora-app` an:
|
|
|
|
1. `GET /accounts/oidc/app/` sagt, wo Authelia ist, gibt die Client-ID und eine vom Server
|
|
signierte nonce (10 min gültig).
|
|
2. Die App führt den Code-Flow mit PKCE im Systembrowser aus. Redirect:
|
|
`xyz.creamfresh.diora:/oauth` bzw. `xyz.creamfresh.diora.reader:/oauth` (Android),
|
|
Loopback `http://127.0.0.1:<port>/oauth` (Desktop, jeder Port). Den Code tauscht sie selbst
|
|
und liest `diora_key` aus userinfo.
|
|
3. `POST /accounts/oidc/app/login/ {id_token}` prüft das Token (`aud` = `diora-app`, nonce) und
|
|
gibt den API-Token des verknüpften Kontos heraus (den vorhandenen, kein neuer).
|
|
4. Die App holt mit dem Token `/accounts/vault/authelia-key/` und öffnet den Bücher-Schlüssel
|
|
wie die Webseite (HKDF, Salt `sub`). Ist dort noch nichts, fragt sie nach dem diora-Passwort.
|