Phase 0: Fundament der Feuerwehr-Verwaltungsplattform
Legt den kompletten Unterbau an, auf dem die Fachmodule aufsetzen. Betrieb: - docker-compose.yml mit Traefik, App, Worker, PostgreSQL 17 + PostGIS, Redis, MinIO und Gotenberg; Entwicklungs-Override ohne Traefik, dafuer mit Mailpit und Adminer - Mehrstufiges Dockerfile: eigener Benutzer, tini als PID 1, Healthcheck - .env.example mit einem Kommentar zu jeder Variable Mandantentrennung: - Row Level Security auf allen mandantenpflichtigen Tabellen - Zwei getrennte Datenbankverbindungen: florian_app mit aktiver RLS fuer die Anwendung, Eigentuemer-Zugang nur fuer Migration, Seed und Anmeldung - fuerOrganisation() als einziger Weg zu Fachdaten Nachvollziehbarkeit: - AuditLog append-only per Trigger; UPDATE ist dauerhaft gesperrt, DELETE nur im gekennzeichneten Aufbewahrungslauf - Geheimnisse werden vor dem Schreiben entfernt Anmeldung: - OIDC gegen ein extern betriebenes authentik, Rollen aus Gruppen nur als Vorbelegung beim ersten Login - Lokaler E-Mail/Passwort-Login als Rueckfallweg - Terminal per PIN und RFID mit zwei Schranken (Geraet, dann Person) Weiteres: - lib/authz.ts mit 9 Rollen und 38 Einzelrechten - lib/storage.ts (MinIO), lib/audit.ts, lib/warteschlange.ts (BullMQ) - Integrationen fuer Mail, Telegram und LLM hinter abschaltbaren Interfaces - tRPC v11, Basis-Layout mit Seitenleiste und dunkler Darstellung - Seed mit einer Beispielwehr: 16 Personen, 3 Fahrzeuge, 40 Geraete - 32 Vitest-Tests (davon 8 gegen eine echte Datenbank), 10 Playwright-Tests - README, Anleitung mit Screenshots, Roadmap, Aufwandsschaetzung, Devlog, Versionierungsregeln und Changelog Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
# Anleitung
|
||||
|
||||
Bedienung von FLORIAN, Stand Phase 0 (Version 0.1.0). Die Bilder zeigen die
|
||||
Beispielwehr aus dem Seed und werden mit `npm run screenshots` neu erzeugt.
|
||||
|
||||
> Was hier noch fehlt — Mitglieder, Geräte, Prüfungen — kommt in den
|
||||
> folgenden Phasen dazu. Der Stand ist bewusst schmal: Phase 0 baut das
|
||||
> Fundament, nicht die Fachmodule. Siehe [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Anmelden
|
||||
|
||||
Aufgerufen wird FLORIAN im Browser unter der Adresse, die der Administrator
|
||||
eingerichtet hat — in der Entwicklung `http://localhost:3100`.
|
||||
|
||||

|
||||
|
||||
Die Anmeldeseite zeigt genau die Wege, die in dieser Installation eingerichtet
|
||||
sind:
|
||||
|
||||
| Weg | Wann |
|
||||
|---|---|
|
||||
| **Anmeldung über authentik** | Der Regelweg, sobald authentik eingerichtet ist. Ein Klick, keine Passworteingabe in FLORIAN. |
|
||||
| **E-Mail und Passwort** | Beim Erststart, und wenn authentik gerade nicht erreichbar ist. |
|
||||
| **Terminal im Gerätehaus** | Am Wandtablet, mit PIN oder Ausweiskarte. Eigene Oberfläche, kommt in Phase 4. |
|
||||
|
||||
Beim allerersten Start gibt es genau ein Konto: das aus `SEED_ADMIN_EMAIL` und
|
||||
`SEED_ADMIN_PASSWORT` in der `.env`. Vorgabe:
|
||||
|
||||
```
|
||||
admin@beispielwehr.de
|
||||
Feuerwehr2026
|
||||
```
|
||||
|
||||
**Dieses Passwort gehört nach dem ersten Login geändert.**
|
||||
|
||||
### Wenn die Anmeldung nicht klappt
|
||||
|
||||
Nach mehreren Fehlversuchen wird das Konto vorübergehend gesperrt — voreingestellt
|
||||
nach fünf Versuchen für fünf Minuten (`KIOSK_MAX_FEHLVERSUCHE`,
|
||||
`KIOSK_SPERRDAUER`). Die Meldung nennt nicht, ob die E-Mail-Adresse oder das
|
||||
Passwort falsch war; das ist Absicht und verhindert, dass sich vorhandene
|
||||
Konten erraten lassen.
|
||||
|
||||
---
|
||||
|
||||
## 2. Die Übersicht
|
||||
|
||||
Nach der Anmeldung steht die Übersicht der eigenen Wehr.
|
||||
|
||||

|
||||
|
||||
Von oben nach unten:
|
||||
|
||||
**Kopfzeile.** Name der Wehr und darunter, wer angemeldet ist und mit welchen
|
||||
Rollen. Wer mehrere Rollen trägt — der Gerätewart ist oft auch Ausbilder —
|
||||
sieht alle.
|
||||
|
||||
**Sicherheitshinweis.** Der gelbe Kasten bleibt stehen, solange die Wehr die
|
||||
Anwendung nicht ausdrücklich freigegeben hat. Er ist keine Formalität:
|
||||
Atemschutzüberwachung und Alarmmonitor sind sicherheitsrelevant, und bis zur
|
||||
Freigabe bleibt die analoge Überwachung führend.
|
||||
|
||||
**Kennzahlen.** Vier Kacheln mit aktiven Mitgliedern, Fahrzeugen, Geräten und
|
||||
fälligen Prüfungen. Sie zählen nur die eigene Wehr — was eine andere Wehr auf
|
||||
derselben Installation hat, ist nicht sichtbar und nicht abfragbar.
|
||||
|
||||
**Stand des Aufbaus.** Welche Phasen fertig sind und was als Nächstes kommt.
|
||||
|
||||
### Die Seitenleiste
|
||||
|
||||
Links stehen alle Module. Was noch nicht existiert, ist ausgegraut und trägt
|
||||
rechts sein Phasenkürzel — `P2` heißt „kommt in Phase 2".
|
||||
|
||||
Was jemand überhaupt sieht, hängt an seinen Rechten. Ein einfaches Mitglied
|
||||
sieht die Mitgliederliste, aber nicht das Protokoll. Wichtig zum Verständnis:
|
||||
das Ausblenden ist nur Bequemlichkeit. Der eigentliche Schutz sitzt auf dem
|
||||
Server — wer eine Adresse direkt aufruft, für die ihm das Recht fehlt, bekommt
|
||||
trotzdem nichts.
|
||||
|
||||
---
|
||||
|
||||
## 3. Helle und dunkle Darstellung
|
||||
|
||||
Oben rechts wird zwischen hell und dunkel umgeschaltet. Voreingestellt ist die
|
||||
Einstellung des Betriebssystems.
|
||||
|
||||

|
||||
|
||||
Die dunkle Darstellung ist bewusst kein invertiertes Schwarz, sondern ein
|
||||
warmes Grau. Wer abends vom Fahrzeug hereinkommt und ans Terminal tritt, soll
|
||||
nicht geblendet werden.
|
||||
|
||||
---
|
||||
|
||||
## 4. Rollen und was sie dürfen
|
||||
|
||||
Neun Rollen, jede mit einem festen Satz Rechte. Die Zuordnung steht zentral in
|
||||
`src/lib/authz.ts` und wird beim Seed in die Datenbank gespiegelt — eine Wehr
|
||||
kann sie also nachträglich zuschneiden, ohne dass jemand Code ändert.
|
||||
|
||||
| Rolle | Darf im Kern |
|
||||
|---|---|
|
||||
| **Administrator** | alles, auch über Wehren hinweg |
|
||||
| **Kommandant** | alles innerhalb der eigenen Wehr, einschließlich Rollen vergeben und Protokoll einsehen |
|
||||
| **Gerätewart** | Fahrzeuge und Geräte anlegen, ändern, aussondern; Prüfungen planen und durchführen |
|
||||
| **Atemschutzgerätewart** | zusätzlich Atemschutzgeräte, Träger und die Überwachung nach FwDV 7 |
|
||||
| **Kassier** | Buchungen und Kassenabschluss (Modul noch nicht geplant, siehe Roadmap) |
|
||||
| **Schriftführer** | Mitglieder pflegen, Einsätze und Berichte erfassen |
|
||||
| **Ausbilder** | Übungen planen, Teilnahme und Qualifikationen erfassen |
|
||||
| **Mitglied** | eigene Daten, Listen lesen |
|
||||
| **Terminal** | nur anmelden und erfassen — nie verwalten |
|
||||
|
||||
Zwei Dinge sind bewusst so gebaut:
|
||||
|
||||
**Der Gerätewart kommt nicht an die Kasse, der Kassier nicht an die Geräte.**
|
||||
Trennung nach Verantwortung, nicht nach Bequemlichkeit.
|
||||
|
||||
**Das Terminal darf fast nichts.** Wer am Wandtablet arbeitet, meldet sich
|
||||
zusätzlich mit PIN oder Karte an und handelt dann unter seiner eigenen Person.
|
||||
Das Terminal selbst ist nur die Tür.
|
||||
|
||||
---
|
||||
|
||||
## 5. Anmeldung über authentik einrichten
|
||||
|
||||
authentik läuft **getrennt** von FLORIAN und ist nicht Teil dieses Stacks.
|
||||
|
||||
### In authentik
|
||||
|
||||
1. Neuen Provider anlegen, Typ **OAuth2/OpenID Provider**
|
||||
2. Client-Typ: **Confidential**
|
||||
3. Redirect-URI genau so eintragen:
|
||||
```
|
||||
https://<ihre-florian-adresse>/api/auth/callback/authentik
|
||||
```
|
||||
4. Scopes: `openid`, `profile`, `email`
|
||||
5. Für die Rollenvorbelegung zusätzlich ein Scope-Mapping, das die Gruppen des
|
||||
Benutzers als Claim `groups` ausliefert
|
||||
6. Eine Application anlegen und mit dem Provider verbinden. Der Slug der
|
||||
Application ist der letzte Teil der Issuer-URL.
|
||||
|
||||
### In der `.env`
|
||||
|
||||
```bash
|
||||
AUTH_OIDC_ENABLED=true
|
||||
AUTH_OIDC_ISSUER=https://auth.beispiel.de/application/o/florian/
|
||||
AUTH_OIDC_CLIENT_ID=<aus authentik>
|
||||
AUTH_OIDC_CLIENT_SECRET=<aus authentik>
|
||||
AUTH_OIDC_GROUP_CLAIM=groups
|
||||
AUTH_OIDC_GROUP_MAPPING=feuerwehr-kommandant:KOMMANDANT,feuerwehr-geraetewart:GERAETEWART
|
||||
AUTH_OIDC_AUTO_PROVISION=true
|
||||
AUTH_OIDC_STANDARD_ORGANISATION=ff-musterhausen
|
||||
```
|
||||
|
||||
Danach die Anwendung neu starten. Auf der Anmeldeseite erscheint der Knopf für
|
||||
authentik.
|
||||
|
||||
### Wie die Rollenvorbelegung wirkt
|
||||
|
||||
Die Zuordnung greift **nur beim allerersten Login** einer Person. Danach ist
|
||||
die in FLORIAN gepflegte Rolle führend, und authentik überschreibt sie nicht
|
||||
mehr. Wer also einem Mitglied in FLORIAN die Rolle „Gerätewart" gibt, behält
|
||||
sie — auch wenn die authentik-Gruppe etwas anderes sagt.
|
||||
|
||||
Steht `AUTH_OIDC_AUTO_PROVISION=false`, muss die Person vorher in FLORIAN
|
||||
angelegt sein. Verknüpft wird über die E-Mail-Adresse.
|
||||
|
||||
### Wenn authentik ausfällt
|
||||
|
||||
Solange `AUTH_LOCAL_ENABLED=true` steht, bleibt der Passwort-Login als
|
||||
Rückfallweg erreichbar. Für den Wirkbetrieb ist es sinnvoll, mindestens ein
|
||||
Konto mit Passwort zu behalten — sonst sperrt ein Ausfall von authentik die
|
||||
ganze Wehr aus.
|
||||
|
||||
---
|
||||
|
||||
## 6. Terminal-Anmeldung im Gerätehaus
|
||||
|
||||
Die Oberfläche dazu kommt in Phase 4; der Anmeldeweg steht bereits.
|
||||
|
||||
Am Terminal wird zweifach geprüft:
|
||||
|
||||
1. **Das Gerät** weist sich mit `KIOSK_DEVICE_SECRET` aus. Ohne diesen Wert
|
||||
nimmt der Server keine PIN und keine Karte an.
|
||||
2. **Die Person** meldet sich mit einer sechsstelligen PIN (zusammen mit ihrer
|
||||
Mitgliedsnummer) oder mit einer RFID-Karte an.
|
||||
|
||||
Beides wird nur als Hash gespeichert — weder die PIN noch die Kartenkennung
|
||||
liegen im Klartext in der Datenbank.
|
||||
|
||||
PINs, die sich zu leicht raten lassen, weist FLORIAN ab: lauter gleiche
|
||||
Ziffern und fortlaufende Folgen wie `123456`.
|
||||
|
||||
Aus dem Seed lässt sich das ausprobieren: Mitgliedsnummer `0001` (Andreas
|
||||
Huber), PIN `471203`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Was protokolliert wird
|
||||
|
||||
Jede schreibende Änderung erzeugt einen Eintrag mit Zeitpunkt, Person,
|
||||
betroffenem Datensatz und den geänderten Feldern im Vorher-Nachher-Vergleich.
|
||||
Auch jede Anmeldung — erfolgreich wie gescheitert.
|
||||
|
||||
Zwei Eigenschaften sind wichtig:
|
||||
|
||||
**Der Log lässt sich nicht ändern.** Nicht über die Oberfläche, nicht über die
|
||||
Datenbank, auch nicht vom Administrator. Die Datenbank selbst verhindert es.
|
||||
|
||||
**Er lässt sich nur im Aufbewahrungslauf kürzen.** Nach der eingestellten
|
||||
Frist (`AUDIT_AUFBEWAHRUNG_TAGE`, Vorgabe 10 Jahre) sichert der
|
||||
Hintergrundprozess die alten Einträge und entfernt sie danach. Das ist der
|
||||
einzige Weg, auf dem etwas aus dem Log verschwindet.
|
||||
|
||||
**Passwörter, PINs und Kartenkennungen stehen nie im Log** — sie werden vor
|
||||
dem Schreiben herausgenommen.
|
||||
|
||||
Einsehbar ist der Log mit dem Recht `system.auditlog`, das Kommandant und
|
||||
Administrator haben. Die Ansicht dazu kommt in Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## 8. Wenn etwas nicht geht
|
||||
|
||||
| Beobachtung | Wahrscheinliche Ursache | Was hilft |
|
||||
|---|---|---|
|
||||
| Die Anwendung startet nicht und meldet fehlende Variablen | `.env` unvollständig | Die Meldung nennt jede fehlende Variable. Erklärungen dazu stehen in `.env.example`. |
|
||||
| Anmeldung schlägt immer fehl, obwohl das Passwort stimmt | Konto nach Fehlversuchen gesperrt | Fünf Minuten warten, oder `gesperrtBis` in der Datenbank zurücksetzen |
|
||||
| Listen sind leer, obwohl Daten da sind | Kein Mandantenkontext gesetzt | Das ist der Schutzmechanismus. Der Zugriff muss über `fuerOrganisation()` laufen. |
|
||||
| Dateien lassen sich nicht hochladen | MinIO nicht erreichbar oder Bucket fehlt | `http://localhost:9011` öffnen; `docker compose logs minio-init` |
|
||||
| Der Knopf für authentik fehlt | Issuer, Client-ID oder Secret nicht gesetzt | Alle drei sind nötig, sonst wird der Weg ausgeblendet |
|
||||
| `docker compose up` startet Traefik mit | Override-Datei wurde nicht mitgeladen | In Produktion `-f docker-compose.yml` angeben |
|
||||
|
||||
Zustand aller Randsysteme auf einen Blick:
|
||||
|
||||
```
|
||||
http://localhost:3100/api/health
|
||||
```
|
||||
|
||||
Antwortet mit `200` und `"status": "ok"`, wenn Datenbank, Objektspeicher und
|
||||
Warteschlange erreichbar sind — sonst mit `503` und der Angabe, welcher Dienst
|
||||
fehlt.
|
||||
|
||||
---
|
||||
|
||||
## Bilder erneuern
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
npm run dev
|
||||
npm run screenshots
|
||||
```
|
||||
|
||||
Die Bilder entstehen in `docs/screenshots/`, jeweils in heller und dunkler
|
||||
Darstellung, und werden mit eingecheckt — die Anleitung soll auch dann lesbar
|
||||
sein, wenn gerade kein Stack läuft.
|
||||
Reference in New Issue
Block a user