feat: Anmelden mit Passkey
Passkeys (WebAuthn/FIDO2) als zweiter Anmeldeweg - auf der Anmeldeseite und bei der erneuten Bestaetigung vor sensiblen Aktionen. Verwaltet unter "Mein Konto". Der Passkey ERSETZT den zweiten Faktor, statt zu ihm hinzuzukommen. Das ist tragbar, weil er zwei Faktoren in sich vereint: Besitz des Geraets und dessen Entsperrung. Damit das keine Annahme bleibt, wird die Nutzerverifikation angefordert (userVerification: 'required') und serverseitig nachgeprueft - eine Antwort ohne sie wird abgewiesen. TOTP muss vorher eingerichtet sein. Ein Passkey haengt an einem Geraet; waere er der einzige Weg hinein, koennte ein verlorenes Handy die Akte schliessen. Aufbau: - Die Pruefung laeuft vollstaendig im Credentials-Provider "passkey", nicht in der Server Action davor. Ein "war schon geprueft" ueber die Grenze zu reichen waere die Stelle, an der sich Anmeldungen faelschen lassen. - Challenges liegen serverseitig und werden per DELETE genau einmal verbraucht - die Datenbank entscheidet, wer sie bekommt. - Bereichskennung und Herkunft kommen aus AUTH_URL, nicht aus einer zweiten Konfigurationsquelle. - Ohne https wird der Knopf gar nicht erst angeboten. - Rate-Limiting, Kontosperre und Audit-Log gelten unveraendert; die Anmeldung nennt den verwendeten Passkey namentlich. Zwei Anmerkungen zur Migration: - "prisma migrate diff" wollte zusaetzlich die Trigramm-Indizes aus prisma/sql/suche.sql loeschen, die Prisma nicht kennt. Die Zeilen sind entfernt und im Migrationskopf vermerkt - ein DROP haette die unscharfe Suche stillgelegt, ohne dass etwas fehlgeschlagen waere. - "prisma format" hat das Schema durchgaengig neu ausgerichtet. Ohne Whitespace betrachtet sind es 88 hinzugefuegte Zeilen und keine geloeschte. Geprueft: 21 Einheitstests, drei End-to-End-Tests gegen Chromiums virtuellen Authenticator (einrichten, anmelden, bestaetigen, entfernen, Geraet ohne Verifikation abgewiesen), beide Geraeteprofile. 496 Tests, null Typfehler, Produktionsbau steht. Nebenbei: Die Zahlen im README waren veraltet (24 statt 33 Modelle, 46 statt 79 Seiten). Sie stehen jetzt mit dem Befehl daneben, mit dem man sie nachzaehlt.
This commit is contained in:
@@ -57,6 +57,10 @@ Anschreiben wieder auf.
|
||||
**Zum Schluss:** Geh auf **Fristen** und klicke einmal auf
|
||||
*Pflichtfristen ableiten*. Danach stehen deine gesetzlichen Termine drin.
|
||||
|
||||
Und wenn du magst: Richte unter **Mein Konto → Passkeys** einen Passkey
|
||||
ein. Dann meldest du dich künftig mit dem Finger an, statt drei Felder
|
||||
auszufüllen. Wie das geht, steht gleich im nächsten Abschnitt.
|
||||
|
||||
---
|
||||
|
||||
## Der Morgen: die Seite „Heute"
|
||||
@@ -328,6 +332,86 @@ Anwendung jederzeit verlassen. Verlangt eine erneute Anmeldung.
|
||||
|
||||
---
|
||||
|
||||
## Anmelden mit dem Finger: Passkeys
|
||||
|
||||
Drei Felder auszufüllen, nur um kurz eine Frist nachzusehen, ist lästig —
|
||||
und lästige Sicherheit wird umgangen. Ein Passkey macht daraus einen
|
||||
Handgriff.
|
||||
|
||||
### Einrichten
|
||||
|
||||
**Mein Konto → Passkeys.** Gib dem Gerät einen Namen, an dem du es später
|
||||
wiedererkennst („iPhone", „Laptop Büro"), und klicke auf *Passkey
|
||||
einrichten*. Dein Gerät fragt nach Fingerabdruck, Gesicht oder PIN —
|
||||
fertig.
|
||||
|
||||
Mach das auf **jedem Gerät**, mit dem du arbeitest. Sie stören sich nicht
|
||||
gegenseitig.
|
||||
|
||||
### Anmelden
|
||||
|
||||
Auf der Anmeldeseite steht unter dem Formular *Mit Passkey anmelden*. Ein
|
||||
Klick, ein Finger, drin. Keine E-Mail, kein Passwort, kein Code.
|
||||
|
||||
### Warum das nicht unsicherer ist
|
||||
|
||||
Es sieht nach weniger aus, ist aber dasselbe — zweimal:
|
||||
|
||||
| | Bisher | Mit Passkey |
|
||||
|---|---|---|
|
||||
| Erster Faktor | Passwort (Wissen) | Das Gerät selbst (Besitz) |
|
||||
| Zweiter Faktor | Code aus der App (Besitz) | Fingerabdruck, Gesicht oder PIN |
|
||||
|
||||
Der entscheidende Zusatz: **Ein Passkey ist an die Adresse gebunden**,
|
||||
unter der du ihn eingerichtet hast. Landest du auf einer nachgebauten
|
||||
Anmeldeseite, funktioniert er dort schlicht nicht — er lässt sich nicht
|
||||
abtippen und nicht weitergeben. Genau das ist der übliche Weg, an einen
|
||||
Code aus der Authenticator-App zu kommen.
|
||||
|
||||
Ein Gerät, das den Passkey ohne Fingerabdruck oder PIN freigibt, wird
|
||||
abgewiesen. Das wäre nur ein Faktor, und dafür ist hier zu viel zu
|
||||
verlieren.
|
||||
|
||||
### Die Authenticator-App bleibt
|
||||
|
||||
Du kannst sie nicht abschaffen, und das ist Absicht. **Ein Passkey hängt
|
||||
an einem Gerät.** Geht das Handy in der Donau baden und wäre der Passkey
|
||||
der einzige Weg hinein, käme niemand mehr an die Akte — auch der Bezirk
|
||||
nicht, auch das Gericht nicht.
|
||||
|
||||
Deshalb: erst Authenticator-App und Wiederherstellungscodes, dann Passkeys.
|
||||
Die Anmeldung mit E-Mail, Passwort und Code bleibt daneben immer offen.
|
||||
|
||||
### Was in der Liste steht
|
||||
|
||||
Unter **Mein Konto → Passkeys** siehst du zu jedem Gerät, wann es
|
||||
eingerichtet und wann es zuletzt benutzt wurde. **Sieh da gelegentlich
|
||||
hinein.** Ein Eintrag, den du nicht zuordnen kannst, gehört sofort
|
||||
entfernt — und danach das Passwort gewechselt.
|
||||
|
||||
Steht bei einem Eintrag *gesichert*, wird der Schlüssel vom Anbieter
|
||||
mitgesichert (iCloud-Schlüsselbund, Google, Bitwarden) und taucht auf
|
||||
einem neuen Gerät von selbst wieder auf. Ohne diesen Vermerk liegt er nur
|
||||
auf diesem einen Gerät.
|
||||
|
||||
### Wenn ein Gerät verloren geht
|
||||
|
||||
1. Auf einem anderen Gerät mit E-Mail, Passwort und Code anmelden.
|
||||
2. **Mein Konto → Passkeys**, den Eintrag des verlorenen Geräts entfernen.
|
||||
3. Fertig — der Schlüssel auf dem Gerät ist damit wertlos.
|
||||
|
||||
Ein Passkey allein reicht ohnehin niemandem, der das Gerät findet: Ohne
|
||||
Fingerabdruck oder PIN gibt das Gerät ihn nicht heraus.
|
||||
|
||||
### Wenn der Knopf gar nicht erscheint
|
||||
|
||||
Dann bietet der Server Passkeys nicht an — das Verfahren verlangt eine
|
||||
verschlüsselte Verbindung (`https`). Oder der Browser ist zu alt. Melde
|
||||
dich mit E-Mail, Passwort und Code an; alles andere funktioniert
|
||||
unverändert.
|
||||
|
||||
---
|
||||
|
||||
## Am Handy
|
||||
|
||||
Die Anwendung ist am Telefon vollwertig bedienbar — sie ist dafür gebaut,
|
||||
|
||||
+51
-5
@@ -131,14 +131,14 @@ cd /opt/betreuungs-cockpit
|
||||
|
||||
# Auf die letzte veröffentlichte Fassung wechseln,
|
||||
# nicht auf den laufenden Entwicklungsstand
|
||||
git checkout v1.0.0
|
||||
git checkout v1.1.0
|
||||
```
|
||||
|
||||
### Kontrolle
|
||||
|
||||
```bash
|
||||
ls docker-compose.yml Dockerfile .env.example # alle drei da
|
||||
git describe --tags # v1.0.0
|
||||
git describe --tags # v1.1.0
|
||||
```
|
||||
|
||||
---
|
||||
@@ -220,6 +220,12 @@ APP_PORT=3000
|
||||
> Steht dort etwas anderes, wird das Sitzungs-Cookie für die falsche
|
||||
> Herkunft gesetzt: Die Anmeldung landet stumm wieder auf dem Formular,
|
||||
> ohne Fehlermeldung. Das ist der Fehler, den man am längsten sucht.
|
||||
>
|
||||
> Seit 1.1.0 hängt noch mehr daran: Aus dieser Adresse wird die Kennung
|
||||
> abgeleitet, an die Passkeys gebunden werden. Steht sie falsch, lässt
|
||||
> sich zwar ein Passkey einrichten, aber nie wieder verwenden. Und ein
|
||||
> späterer Wechsel der Domain macht alle vorhandenen Passkeys
|
||||
> unbrauchbar — das ist kein Fehler, sondern der Phishingschutz.
|
||||
|
||||
**Läuft auf dem Server schon etwas auf Port 3000?** Dann weich aus:
|
||||
|
||||
@@ -293,7 +299,7 @@ betreuungs-cockpit-sicherung-1 Up
|
||||
```bash
|
||||
# Antwortet die Anwendung? (Port anpassen, falls du APP_PORT gesetzt hast)
|
||||
curl -s http://127.0.0.1:3000/api/gesundheit
|
||||
# {"status":"bereit","version":"1.0.0","commit":"..."}
|
||||
# {"status":"bereit","version":"1.1.0","commit":"..."}
|
||||
```
|
||||
|
||||
Kommt hier nichts, sieh in die Logs:
|
||||
@@ -643,7 +649,7 @@ Erst **vom Proxy-Server aus** — kommt er überhaupt an?
|
||||
|
||||
```bash
|
||||
curl -s http://10.0.0.5:3000/api/gesundheit
|
||||
# {"status":"bereit","version":"1.0.0",...}
|
||||
# {"status":"bereit","version":"1.1.0",...}
|
||||
```
|
||||
|
||||
Kommt hier nichts, liegt es an 7a: falsche `APP_BIND`, Firewall, oder der
|
||||
@@ -694,7 +700,7 @@ Zum Schluss: **Fristen → Pflichtfristen ableiten**.
|
||||
- Du kommst mit E-Mail, Passwort und dem Code aus der App herein.
|
||||
- Unter **Fristen** stehen Einträge mit Paragraphenbezug.
|
||||
- Unter **Protokoll** siehst du deine eigene Anmeldung.
|
||||
- Unten links in der Seitenleiste steht `Version 1.0.0 · <commit>`.
|
||||
- Unten links in der Seitenleiste steht `Version 1.1.0 · <commit>`.
|
||||
|
||||
---
|
||||
|
||||
@@ -1024,6 +1030,46 @@ timedatectl
|
||||
docker compose exec app date
|
||||
```
|
||||
|
||||
### Der Knopf „Mit Passkey anmelden" erscheint nicht
|
||||
|
||||
Passkeys brauchen einen sicheren Kontext. Die Anwendung bietet sie nur an,
|
||||
wenn `AUTH_URL` auf `https://` steht (oder auf `localhost`) — sonst
|
||||
erscheint der Knopf bewusst gar nicht erst, statt beim Klick mit einer
|
||||
kryptischen Browsermeldung abzubrechen.
|
||||
|
||||
```bash
|
||||
docker compose exec app printenv AUTH_URL # muss mit https:// beginnen
|
||||
```
|
||||
|
||||
Steht dort `http://`, korrigiere die `.env` und starte neu:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Erscheint der Knopf weiterhin nicht, kann der Browser kein WebAuthn — das
|
||||
betrifft praktisch nur sehr alte Fassungen.
|
||||
|
||||
### Ein Passkey lässt sich einrichten, aber nicht zum Anmelden benutzen
|
||||
|
||||
Fast immer stimmt die Adresse nicht überein. Der Schlüssel ist an den
|
||||
Hostnamen aus `AUTH_URL` gebunden; der Browser vergleicht ihn mit dem, was
|
||||
in der Adresszeile steht.
|
||||
|
||||
```bash
|
||||
docker compose exec app printenv AUTH_URL
|
||||
# muss exakt die Adresse sein, die du im Browser eintippst
|
||||
```
|
||||
|
||||
Typische Ursachen: `AUTH_URL` zeigt auf `cockpit.example.de`, aufgerufen
|
||||
wird aber `www.cockpit.example.de` — oder umgekehrt. Ein Passkey für die
|
||||
eine Adresse gilt auf der anderen nicht.
|
||||
|
||||
**Nach einem Wechsel der Domain** müssen alle Passkeys neu eingerichtet
|
||||
werden. Die alten Einträge lassen sich unter *Mein Konto → Passkeys*
|
||||
entfernen. Die Anmeldung mit E-Mail, Passwort und Code funktioniert in der
|
||||
Zwischenzeit unverändert — man sperrt sich damit also nicht aus.
|
||||
|
||||
### Die Texterkennung findet nichts
|
||||
|
||||
```bash
|
||||
|
||||
+34
-1
@@ -9,7 +9,7 @@ Zeit spart. Er steht weit unten, wenn er nur schön wäre.
|
||||
Diese Datei ist eine Absichtserklärung, kein Vertrag. Was sich im Betrieb
|
||||
als wichtiger herausstellt, rutscht nach oben.
|
||||
|
||||
Stand: 22.08.2026 · Aktuelle Fassung: **1.0.0** ([CHANGELOG](../CHANGELOG.md))
|
||||
Stand: 22.08.2026 · Aktuelle Fassung: **1.1.0** ([CHANGELOG](../CHANGELOG.md))
|
||||
|
||||
---
|
||||
|
||||
@@ -136,6 +136,31 @@ Jahresbericht und bei einer Höherstufung gebraucht.
|
||||
|
||||
---
|
||||
|
||||
## 1.2b — Passkeys abrunden
|
||||
|
||||
Der Grundstock steht seit 1.1.0. Was noch fehlt, ist Bequemlichkeit, nicht
|
||||
Sicherheit.
|
||||
|
||||
### Passkey direkt im Anmeldefeld anbieten
|
||||
|
||||
Browser können einen Passkey im Ausfüllvorschlag des E-Mail-Feldes
|
||||
anbieten (*conditional UI*), statt hinter einem eigenen Knopf. Ein Schritt
|
||||
weniger. Braucht einen zweiten Challenge-Weg und sorgfältiges Abbrechen,
|
||||
wenn stattdessen doch das Passwort getippt wird.
|
||||
|
||||
### Passkey umbenennen
|
||||
|
||||
Heute geht nur anlegen und entfernen. Wer sein Gerät „Handy" genannt hat
|
||||
und inzwischen zwei hat, muss den Passkey neu einrichten.
|
||||
|
||||
### Warnen, wenn der letzte Passkey nur auf einem Gerät liegt
|
||||
|
||||
Die Liste sagt bereits, ob ein Schlüssel gesichert ist. Wer ausschließlich
|
||||
ungesicherte Passkeys hat und dazu keine Wiederherstellungscodes mehr,
|
||||
sollte das deutlicher zu sehen bekommen als in einer Zeile Kleingedrucktem.
|
||||
|
||||
---
|
||||
|
||||
## 1.3 — Belastbarkeit
|
||||
|
||||
**Ziel:** Die Stellen absichern, die bisher nur unter Laborbedingungen
|
||||
@@ -164,6 +189,14 @@ Monat automatisch durchspielt — Sicherung zurückspielen, Diagnosen im
|
||||
Klartext prüfen, Testdatenbank verwerfen — wäre der Beweis statt der
|
||||
Absichtserklärung.
|
||||
|
||||
### Passkey-Ablehnung ohne Browser nachstellen
|
||||
|
||||
Die Prüfung „Passkey ohne Nutzerverifikation wird abgewiesen" ist im Code
|
||||
doppelt abgesichert und im Browsertest belegt — dort weist allerdings
|
||||
schon der Browser ab, der Server kommt gar nicht dran. Ein Test, der die
|
||||
Antwort von Hand baut und den Browser umgeht, würde den serverseitigen
|
||||
Rückhalt tatsächlich beweisen.
|
||||
|
||||
### Barrierefreiheits-Durchgang mit einem Screenreader
|
||||
|
||||
Semantik, Fokusreihenfolge und Kontraste sind beim Bauen beachtet und in
|
||||
|
||||
Reference in New Issue
Block a user