Anmeldung über einen generischen OIDC-Anbieter (Ziel authentik) mit Discovery, PKCE, state und nonce, dazu ein lokaler Login mit argon2 als Notfallzugang. Die Rechte stehen in einer Tabelle, die Prüfung liegt ausschließlich serverseitig, und Rolle wie Aktiv-Status werden bei jeder Anfrage aus der Datenbank gelesen — eine entzogene Rolle greift damit sofort, ein abgeschaltetes Konto kommt nicht mit gültigem Cookie noch zwölf Stunden weiter. Kein Auth.js: v5 steht dauerhaft auf Beta, das Projekt ist im Wartungsmodus und sein Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen openid-client und jose, wie in ProxLedger. Die API von openid-client 6 wurde vorher gegen die Typdefinitionen im Paket geprüft, nicht aus dem Gedächtnis geschrieben. Ein unabhängiger Review hat einen Blocker und acht weitere Befunde gefunden, alle behoben. Der wichtigste war eine offene Weiterleitung: die Prüfung des Rücksprungziels verglich Präfixe, und "/<TAB>/fremd.example" besteht alle drei Bedingungen. Browser entfernen Tabulator, Zeilenumbruch und Wagenrücklauf beim Verarbeiten einer URL, übrig bleibt "//fremd.example" und damit ein fremder Host — genau das Phishing, das die Prüfung abwehren sollte. Sie löst das Ziel jetzt gegen eine Wegwerf-Basis auf und vergleicht die Herkunft. Weitere Befunde: jede OIDC-Anmeldung hätte ohne konfigurierte Gruppen die Rolle still auf LESER gesetzt, bei einem einzigen Administrator ohne Rückweg; die Verknüpfung über die E-Mail-Adresse geschah ohne email_verified und wäre der Weg gewesen, den lokalen Notfall-Administrator zu übernehmen; der letzte aktive Administrator konnte sich selbst herabstufen. Drei Fehler waren meine eigenen. Der Platzhalter-Hash gegen den Zeitkanal war erfunden und hätte sofort geworfen statt Zeit zu kosten, die Zusage wäre also falsch gewesen. nextUrl.origin lieferte im Test localhost statt 127.0.0.1 und wäre hinter einem Reverse Proxy der interne Host; alle Umleitungen gehen jetzt über APP_URL. Und overflow-x: hidden auf body hat die eigene Mobil-Prüfung blind gemacht — auf der Benutzerseite war bei 390 px ein Knopf abgeschnitten, ohne dass scrollWidth es zeigen konnte. 274 Tests, darunter die vollständige Rechtematrix, die Angriffe auf das Rücksprungziel, die Trennung von Sitzungs- und Kurzzeit-Token und ein struktureller Nachweis, dass jede Server Action mit einer Rechteprüfung beginnt. Closes #7 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4.0 KiB
4.0 KiB
Changelog
Alle nennenswerten Änderungen an Laufzettel werden in dieser Datei dokumentiert.
Das Format basiert auf Keep a Changelog, und dieses Projekt folgt Semantic Versioning.
[Unveröffentlicht]
Hinzugefügt
- Projektgerüst: Next 16 mit App Router und TypeScript strict, Tailwind 4, Vitest 5, Playwright 1.63, ESLint flat config, Prettier, husky mit lint-staged und commitlint (#005)
- Gestaltung: Design-Token als CSS-Variablen, selbst gehostete Schriften, Wortmarke und
Bildmarke
[ ✓ ], Versionsanzeige unten links (#005) - Betriebsstand mit Docker Compose: die Datenbank ist von außen nicht erreichbar — kein veröffentlichter Port und ein Netz ohne Gateway; Migrationen laufen beim Start in einem eigenen Einmal-Dienst, der Seed bleibt ein bewusster Handgriff (#005)
- Healthcheck
GET /api/gesundheitund Sicherheitskopfzeilen samt CSP-Nonce je Anfrage (#005) - shadcn/ui eingerichtet: sein Variablen-Vertrag wird aus den Laufzettel-Token gespeist, damit kopierte Bauteile ohne Nacharbeit passen (#005)
pnpm pruefe:dokuhält Dokumentation und Code zusammen: Version im Changelog, Issue-Dateien gegen ihre Übersicht, keine Verweise auf fehlende Dateien, keine fehlenden Bilder (#005)pnpm docs:screenshotserzeugt Screenshots reproduzierbar und prüft dabei gleich, dass am Handy nicht horizontal gescrollt wird (#005)- Issues, Labels und Meilensteine für alle vier Phasen im Gitea-Tracker (#001–#019)
- Vollständiges Datenmodell in deutscher Benennung: 20 Tabellen, 9 Aufzählungen, erste Migration, idempotenter Seed für Betriebsmodus, eigene Organisation und Administrator (#006)
- Erzeugung der
tagCodes für die QR-Aufkleber, mit Vergabebuch, aus dem nie gelöscht wird — ein Code kommt auch nach dem Löschen eines Geräts nicht zurück (#006) - Verschlüsselung von Geheimnissen mit AES-256-GCM aus
APP_SECRET, für das LDAP-Bind-Passwort und später die Zugangsdaten der Connectoren (#006) - Anmeldung über einen generischen OIDC-Anbieter mit Discovery, PKCE,
stateundnonce; unbekannte Benutzer entstehen beim ersten Login, die Rolle kommt bei jeder Anmeldung neu aus den Gruppen (#007) - Lokale Anmeldung mit E-Mail und Passwort als Notfallzugang, nur bei
AUTH_LOCAL_ENABLED=true(#007) - Rechte in einer Tabelle mit vier Rollen; geprüft ausschließlich serverseitig, Rolle und Aktiv-Status pro Anfrage aus der Datenbank (#007)
- Benutzerverwaltung für Administratoren: Rolle setzen, Konto abschalten, lokales Konto anlegen (#007)
- Navigation zeigt nur, was die eigene Rolle darf (#007)
Sicherheit
- Der Compose-Stand bricht ab, wenn
APP_SECRET,POSTGRES_PASSWORDoderAPP_URLfehlen, statt mit Standardwerten zu starten (#005) - Die Mandantengrenze steht als zusammengesetzter Fremdschlüssel in der Datenbank: ein Lauf kann strukturell kein Gerät, keine Person und keine Abteilung eines fremden Kunden wählen (#006)
- Ein Bind-Passwort ohne Versionspräfix wird von der Datenbank abgewiesen, ein versehentlich im Klartext geschriebenes fällt damit sofort auf (#006)
- Ein Lauf nimmt seine Schrittergebnisse und Kommentare beim Löschen nicht mehr mit, und ein Protokolleintrag kann seinen Urheber nicht mehr verlieren (#006)
- Das Rücksprungziel nach der Anmeldung wird aufgelöst und gegen die eigene Herkunft geprüft,
statt nur Präfixe zu vergleichen —
/<TAB>/fremd.examplewäre sonst eine offene Weiterleitung gewesen (#007) - Alle Umleitungen gehen über
APP_URLstatt über die Herkunft der Anfrage; hinter einem Reverse Proxy zeigten sie sonst auf den internen Host (#007) - Eine Anmeldung über den Anbieter verknüpft nur mit einem bestehenden Konto, wenn die Adresse bestätigt ist (#007)
- Ohne eingerichtete
OIDC_GROUP_*-Zuordnung bleibt die Rolle unangetastet, statt jeden aufLESERherabzustufen (#007) - Der letzte aktive Administrator kann sich weder herabstufen noch abschalten (#007)
- Beim Abschalten eines Kontos werden laufende Sitzungen sofort ungültig (#007)