feat(2.9.0): Hardwarekey an der WebUI-Anmeldung und die Roadmap als Tafel
Zwei angeforderte Funktionen, dazu zwei Befunde, die ihnen im Weg lagen. == Zweite Stufe der Anmeldung (#666) == Nach korrektem Passwort verlangt das WebUI einen registrierten FIDO2-Schluessel. Die Pflicht haengt je Konto daran, OB ein Schluessel registriert ist - nicht an einem Schalter. Ein Schalter, den man vor der Registrierung umlegt, sperrt den Betreiber aus seiner eigenen Verwaltung aus. Ohne Fremdbibliothek gebaut (das Projekt hat bewusst kein Composer): src/Core/Webauthn/ mit CBOR-Leser, COSE-nach-DER-Wandlung und Pruefung ueber openssl_verify (ES256, RS256). Auth::login() heisst jetzt Auth::startLogin() und liefert drei Zustaende statt eines bool - aus "wahr heisst drin" ist "wahr heisst vielleicht drin" geworden, und ein uebersehener Aufrufer soll an einem fehlenden Methodennamen scheitern statt an einer truthy Zeichenkette. Die Setup-API der Zielsysteme geht ueber verifyCredentials() daran vorbei und bleibt unberuehrt. Notfalltoken als Ersatzweg: nur als Hash in der Datenbank, Klartext einmalig angezeigt, Einmalgebrauch. Laeuft als gewoehnliches Formular ohne JavaScript - ein Ersatzweg mit denselben Voraussetzungen wie der Hauptweg waere keiner. Fehlversuche auf der zweiten Stufe laufen in dieselbe Konto- und IP-Sperre wie das Passwort; zuvor zaehlte nur die erste Stufe. Benutzerverwaltung kann Konten umbenennen. Beim Loeschen eines Kontos und beim Zuruecksetzen der Testinstanz werden seine Schluessel mitgenommen: credential_id ist geraeteweit eindeutig, eine verwaiste Zeile haette denselben Yubikey dauerhaft unbrauchbar gemacht. == Roadmap-Tafel (#664) == Neuer Unterreiter Roadmap -> Tafel. Der Server liefert die fertige Gliederung (RoadmapBoard, ?action=board), die Seite rendert nur - sonst schneidet das WebUI anders als die API und ein spaeterer Export ein drittes Mal anders. Phasen stehen nicht im Code: gepflegt in der neuen Tabelle roadmap_phases, sonst an den groessten Luecken der Rangfolge geschnitten und sichtbar als geraten gekennzeichnet. Links die gekappte Spalte des Erledigten, rechts "Wird ueberlegt". Nachpruefung ist ein eigener Zustand und wird nicht als erledigt gezeichnet. Klick oeffnet ein Seitenpanel: Volltext, Verlauf, Kommentieren mit action_taken, Aendern von Bereich, Rang, Zielfassung, Status und Ausloeser - ohne Seitenwechsel. Das war der eigentliche Gewinn: Wer etwas bemerkte, musste sich bisher die Nummer merken und im Bugtracker danach suchen. Tiefe Verweise #roadmap/<projekt>/item/<id>. Ziehen zwischen Phasen ueber ?action=rank_between. Fremdtext wird ausschliesslich ueber textContent gesetzt, nie ueber innerHTML. == Befund: roadmap_rank war projektweit und bereichslokal zugleich (#177) == Entschieden: projektweit. move() sucht den Nachbarn ohne Bereichsfilter, reorder() schreibt keinen ganzen Bereich mehr auf 10/20/30 um, sondern vertauscht nur die Raenge, die die Eintraege ohnehin halten. rank_between rechnet die Mitte zwischen zwei Nachbarn und nummeriert nur dann einmal neu - unter Erhalt der Reihenfolge -, wenn dort kein Platz mehr ist; die Antwort sagt es dann. == Befund: CSRF an sitzungsauthentifizierten API-Schreibzugriffen == ApiAuth akzeptiert die WebUI-Sitzung, die Endpunkte prueften dabei kein CSRF-Token. Der Kommentar in Http::beginJson - "ausschliesslich per Token-Header, niemals per Cookie" - traf auf die API nie zu. Schreibende Anfragen im Sitzungsweg verlangen jetzt csrf_token bzw. X-CSRF-Token. Token-Clients sind nicht betroffen und wurden gegengeprueft. == Geprueft == tests/e2e/suite_tafel.py neu (16 Faelle), Suite gesamt 172 von 172 gegen die Testinstanz. Die WebAuthn-Zeremonien zusaetzlich gegen einen Software- Authenticator mit echtem P-256 und echtem CBOR: Registrieren, Anmelden, verfaelschte Signatur, veraltete Herausforderung und Zaehler-Rueckfall als Klonverdacht werden je mit 403 abgewiesen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
edea46e342
commit
f0ed67ad29
@@ -588,3 +588,89 @@ Rückgabewert 2.
|
||||
|
||||
Zum Abschalten die erzeugten `.htaccess`-Dateien in den Produktverzeichnissen
|
||||
löschen. Sie entstehen beim nächsten Auslöser neu.
|
||||
|
||||
---
|
||||
|
||||
## 17. Umstellung auf 2.9
|
||||
|
||||
Zwei Dinge kommen dazu: ein Hardwarekey als zweite Stufe der WebUI-Anmeldung
|
||||
und die Roadmap-Tafel. Nur das Erste braucht Aufmerksamkeit — beim Zweiten
|
||||
kann nichts schiefgehen, was sich nicht durch Neuladen beheben ließe.
|
||||
|
||||
### 17.1 Die Reihenfolge, und warum sie zählt
|
||||
|
||||
```
|
||||
1. Deployment
|
||||
2. Migration 019 ausführen
|
||||
3. Anmelden, Hardwarekey registrieren
|
||||
4. Notfalltoken erzeugen und ausdrucken
|
||||
```
|
||||
|
||||
**Die Pflicht zum zweiten Faktor hängt daran, ob ein Schlüssel registriert
|
||||
ist — nicht an einem Schalter.** Das ist Absicht und der Grund, warum diese
|
||||
Reihenfolge ungefährlich ist: Solange kein Schlüssel hinterlegt ist, läuft die
|
||||
Anmeldung genau wie vorher. Ein Schalter, den man vor der Registrierung
|
||||
umlegt, hätte die entgegengesetzte Eigenschaft.
|
||||
|
||||
Nach Schritt 3 ist das Passwort allein nicht mehr genug. Deshalb gehört
|
||||
Schritt 4 unmittelbar dahinter und nicht auf die Liste für nächste Woche.
|
||||
|
||||
### 17.2 Der Schlüssel gilt nur für eine Adresse
|
||||
|
||||
Ein auf `dc.mhdf.de` registrierter Yubikey funktioniert auf
|
||||
`dc-dev.mhdf.de` **nicht**, und umgekehrt. Das ist keine Einschränkung der
|
||||
Umsetzung, sondern der Kern des Verfahrens: Genau deshalb läuft ein
|
||||
Anmeldeversuch auf einer nachgebauten Seite ins Leere. Auf beiden Instanzen
|
||||
also je einmal registrieren.
|
||||
|
||||
Registriert wird unter **System → 👤 Benutzer → Meine Hardwarekeys**.
|
||||
Registrieren Sie zwei, wenn Sie zwei haben — der Notfalltoken ist der
|
||||
Ersatzweg, nicht der zweite Schlüssel.
|
||||
|
||||
### 17.3 Der Notfalltoken
|
||||
|
||||
Fünf Gruppen zu vier Zeichen, **einmalig** bei der Erzeugung angezeigt. In der
|
||||
Datenbank liegt nur ein Hash; wer sie liest, hält damit keinen Zweitschlüssel
|
||||
in der Hand. Er gilt für **genau eine** Anmeldung und ist danach verbraucht —
|
||||
die Meldezeile sagt es Ihnen.
|
||||
|
||||
Beim Eintippen zählen nur Buchstaben und Ziffern. Bindestriche und
|
||||
Groß-/Kleinschreibung sind gleichgültig.
|
||||
|
||||
### 17.4 Wenn Schlüssel *und* Notfalltoken weg sind
|
||||
|
||||
Dann hilft nur die Datenbank. Der Weg zurück ist, die Schlüssel des Kontos zu
|
||||
entfernen — danach genügt wieder das Passwort allein:
|
||||
|
||||
```sql
|
||||
DELETE FROM dc_user_credentials
|
||||
WHERE user_id = (SELECT id FROM dc_users WHERE username = '<name>');
|
||||
```
|
||||
|
||||
### 17.5 Konto umbenennen
|
||||
|
||||
**System → 👤 Benutzer** kann jetzt auch den Anmeldenamen ändern. Registrierte
|
||||
Hardwarekeys hängen an der Konto-ID und überstehen das; offene Sperrfenster des
|
||||
alten Namens werden dabei aufgeräumt.
|
||||
|
||||
### 17.6 Was sich an der API ändert
|
||||
|
||||
- Sitzungsauthentifizierte **Schreibzugriffe** auf die API verlangen jetzt ein
|
||||
CSRF-Token (`csrf_token` im Rumpf oder der Kopf `X-CSRF-Token`).
|
||||
Token-Clients — Agenten, MCP-Adapter, `scripts/dc.py` — sind **nicht**
|
||||
betroffen: Sie schicken keinen Cookie. Betroffen sind nur eigene Skripte,
|
||||
die sich auf eine WebUI-Sitzung stützen.
|
||||
- `roadmap_rank` gilt **projektweit**, nicht je Bereich (#177). `move` sucht
|
||||
den Nachbarn entsprechend ohne Bereichsfilter, und `reorder` zählt nicht mehr
|
||||
je Bereich bei 10 neu durch, sondern vertauscht nur die Ränge, die die
|
||||
genannten Einträge ohnehin halten. Wer bisher je Bereich ab 10 vergeben hat,
|
||||
sollte die Reihenfolge einmal durchsehen.
|
||||
|
||||
### 17.7 Prüfen, ob alles läuft
|
||||
|
||||
- [ ] Anmeldung mit Passwort führt auf die zweite Stufe
|
||||
- [ ] Hardwarekey schließt sie ab
|
||||
- [ ] Notfalltoken schließt sie ebenfalls ab und ist danach verbraucht
|
||||
- [ ] Roadmap → 🧭 Tafel zeigt Spalten, Spuren und Kärtchen
|
||||
- [ ] Ein Klick auf ein Kärtchen öffnet das Panel, Kommentieren funktioniert
|
||||
- [ ] `python tests/e2e/run.py` gegen die Testinstanz: alles grün
|
||||
|
||||
Reference in New Issue
Block a user