forked from jappel/leistungsbilanz-ts
Document authenticated deployment
This commit is contained in:
parent
847f4e9493
commit
3670f02ccd
9 changed files with 97 additions and 20 deletions
|
|
@ -7,10 +7,9 @@ Zugang wird von einem Administrator über **Config → Benutzer** angelegt.
|
|||
|
||||
Es gibt genau zwei Rollen (`src/shared/constants/user-roles.ts`):
|
||||
|
||||
- **admin** – wie `user`, zusätzlich Zugriff auf den Config-Bereich
|
||||
(Benutzerverwaltung, eigenes Konto)
|
||||
- **admin** – wie `user`, zusätzlich Zugriff auf **Config → Benutzer**
|
||||
- **user** – volle Nutzung der Leistungsbilanz (Projekte, Stromkreislisten,
|
||||
Grobbilanz, …), kein Zugriff auf Config
|
||||
Grobbilanz, …) und Zugriff auf das eigene Konto, keine Benutzerverwaltung
|
||||
|
||||
Es gibt (bewusst, siehe unten) keine konfigurierbare Rechte-Matrix und keine
|
||||
Verknüpfung mit einer Mitarbeiter-/Personen-Stammdatentabelle.
|
||||
|
|
@ -44,6 +43,8 @@ Verknüpfung mit einer Mitarbeiter-/Personen-Stammdatentabelle.
|
|||
- **Frontend:** `AuthProvider` lädt `/api/auth/me`; `LoginGate`
|
||||
(`src/app/layout.tsx`) zeigt Login- bzw. Passwortformular, solange nicht
|
||||
angemeldet bzw. `mustChangePassword` gesetzt ist, sonst die App.
|
||||
**Konto** ist für jede angemeldete Person erreichbar; **Benutzer** nur für
|
||||
Administratoren.
|
||||
|
||||
## Schutzmechanismen
|
||||
|
||||
|
|
@ -52,12 +53,12 @@ Verknüpfung mit einer Mitarbeiter-/Personen-Stammdatentabelle.
|
|||
| Passwort-Raten (online) | `LoginThrottle`: 5 Fehlversuche je Anmeldename in 15 min → HTTP 429 bis das Fenster abläuft. Schlüssel ist der Name, nicht die IP (alle Anfragen kommen über den Next-Rewrite mit derselben Quell-IP). |
|
||||
| Passwort-Raten (offline, DB-Diebstahl) | scrypt (N=16384, r=8, p=1), eigenes Salt je Hash |
|
||||
| Nutzernamen ausspähen (Timing) | Unbekannte Namen werden gegen einen festen Dummy-Hash geprüft – immer genau ein scrypt-Lauf; einheitliche Fehlermeldung |
|
||||
| CSRF | SameSite=Lax **plus** `requireSameOriginForWrites`: schreibende Anfragen nur mit `Sec-Fetch-Site: same-origin`. Nötig, weil andere Dienste auf demselben Host (andere Ports) als „same-site“ gelten |
|
||||
| CSRF über reines HTTP | Browser senden `Sec-Fetch-Site` nur an HTTPS-Adressen. Beim Zugriff per `http://…` im LAN greift deshalb nur SameSite=Lax (schützt gegen fremde Domains, nicht gegen andere Ports desselben Hosts). |
|
||||
| CSRF | SameSite=Lax **plus** `requireSameOriginForWrites`: jeder Schreibzugriff braucht `X-Leistungsbilanz-Request: 1`; fremde HTML-Formulare können den Header nicht setzen und ein fremder Fetch scheitert an der notwendigen CORS-Preflight-Anfrage. Ein vorhandener `Sec-Fetch-Site`-Header wird zusätzlich geprüft. |
|
||||
| CSRF über reines HTTP | Der eigene Anwendungsheader gilt auch dann, wenn Browser im HTTP-LAN keinen `Sec-Fetch-Site`-Header senden. Skripte und manuelle API-Aufrufe müssen ihn bei `POST`, `PUT`, `PATCH` und `DELETE` ausdrücklich mitsenden. |
|
||||
| Session-Fixation | Neue Session-ID bei jedem Login und nach jedem Passwortwechsel |
|
||||
| Gestohlenes Cookie | httpOnly; gleitende Laufzeit (`AUTH_SESSION_HOURS`, 12 h) **und** harte Obergrenze (`AUTH_SESSION_MAX_DAYS`, 7 Tage) |
|
||||
| Kompromittiertes Konto | Passwort-Reset und Deaktivieren beenden sofort alle Sessions des Kontos; Passwortwechsel beendet alle anderen |
|
||||
| Aussperren | Der letzte aktive Admin kann weder herabgestuft noch deaktiviert werden; eigener Zugang ist in der Liste gesperrt |
|
||||
| Aussperren | Der letzte aktive Admin kann weder herabgestuft noch deaktiviert werden; Prüfung und Änderung laufen atomar in einer SQLite-Transaktion. Der eigene Zugang ist in der Liste gesperrt. |
|
||||
| Rechteausweitung / Mass Assignment | Rolle kommt aus der DB, nie aus der Anfrage; zod-Schemas mit `.strict()` |
|
||||
| DoS über riesige Passwörter | Längengrenzen (Name 100, Passwort 200 Zeichen) vor scrypt |
|
||||
| Look-alike-Namen | Anmeldename nur `a-z 0-9 . _ @ -`, klein geschrieben |
|
||||
|
|
@ -77,6 +78,18 @@ ignoriert; jeder weitere Zugang entsteht über Config → Benutzer. Die Werte
|
|||
gehören in eine lokale, nicht versionierte `.env` auf dem Deploy-Host, nicht
|
||||
in `compose.yaml` selbst.
|
||||
|
||||
```dotenv
|
||||
AUTH_BOOTSTRAP_USERNAME=admin
|
||||
AUTH_BOOTSTRAP_PASSWORT=<eigenes Startpasswort mit mindestens 12 Zeichen>
|
||||
AUTH_SESSION_HOURS=12
|
||||
AUTH_SESSION_MAX_DAYS=7
|
||||
COOKIE_SECURE=false
|
||||
```
|
||||
|
||||
Bei Zugriff über HTTPS muss `COOKIE_SECURE=true` gesetzt werden. Bei direktem
|
||||
HTTP-Zugriff im LAN bleibt es `false`, da der Browser das Session-Cookie sonst
|
||||
nicht speichert.
|
||||
|
||||
## Bewusst nicht umgesetzt (Stand jetzt)
|
||||
|
||||
- Konfigurierbare Rechte-Matrix statt fixer `admin`/`user`-Rollen
|
||||
|
|
|
|||
|
|
@ -33,6 +33,15 @@ mit Hot Reload. Das Frontend leitet `/api/*` über `API_INTERNAL_URL` an die API
|
|||
weiter; beim gebauten Stand wird das Ziel zur Build-Zeit festgelegt. Die
|
||||
Datenbank liegt über einen Host-Mount außerhalb des Containers.
|
||||
|
||||
Vor allen fachlichen `/api/*`-Routen liegt die Cookie-basierte Anmeldung aus
|
||||
`src/server/auth`. Nur Login, Status und Logout sind ohne Session erreichbar.
|
||||
Schreibzugriffe verlangen zusätzlich den Anwendungsheader
|
||||
`X-Leistungsbilanz-Request: 1`, damit der Schutz auch beim HTTP-Zugriff im LAN
|
||||
ohne Fetch-Metadata-Header greift. `/api/users/*` ist Administratoren
|
||||
vorbehalten. Konten sind globale Betriebsdaten und gehören nicht zu
|
||||
Projektkommandos, Snapshots oder Projekttransfers. Sessions liegen im Speicher
|
||||
des einzelnen API-Prozesses und enden bei dessen Neustart.
|
||||
|
||||
Neue IDs für Stromkreise, Gerätezeilen, Gruppen und Verteilerkomponenten erzeugt
|
||||
der Editor über `src/frontend/utils/client-id.ts` (`createClientId`). Die
|
||||
Hilfsfunktion nutzt `crypto.randomUUID()`, sofern verfügbar, andernfalls
|
||||
|
|
@ -57,6 +66,11 @@ vor dem Command und bleiben über Undo/Redo unverändert.
|
|||
- `src/frontend/components/circuit-grid-*.ts` – reine Grid-Projektion,
|
||||
Zellbesitz, Einfügen und Sicherheitsregeln
|
||||
- `src/frontend/utils/api.ts` – typisierte Frontend-API-Aufrufe
|
||||
- `src/frontend/components/AuthProvider.tsx` und `LoginGate.tsx` – Anmeldung
|
||||
und erzwungener Wechsel eines Startpassworts
|
||||
- `src/server/auth/` – Passwort-Hashing, Sessions und Zugriffsschutz
|
||||
- `src/domain/services/user.service.ts` und `src/domain/ports/user.store.ts` –
|
||||
kontobezogene Regeln und schmale Persistenzgrenze
|
||||
- `src/server/index.ts` und `src/server/routes/` – API-Komposition
|
||||
- `src/domain/services/` – fachliche Command- und Synchronisierungsregeln
|
||||
- `src/server/composition/` – Verdrahtung fachlicher Services mit konkreten
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ und macOS keine inotify-Events durchreichen; das kostet dauerhaft CPU, auch
|
|||
ohne Benutzeraktivität. Dieser Stack gehört deshalb nur auf einen
|
||||
Entwicklungsrechner.
|
||||
|
||||
Beide Stacks enthalten weder TLS, Authentifizierung, einen vorgeschalteten
|
||||
Reverse Proxy noch ein zentral betriebenes Datenbanksystem. Die oben genannten
|
||||
Container-Schutzmaßnahmen ersetzen diese Funktionen nicht. Der Stack darf
|
||||
deshalb nicht öffentlich erreichbar gemacht werden.
|
||||
Beide Stacks enthalten Anmeldung mit festen Rollen, aber weder TLS, einen
|
||||
vorgeschalteten Reverse Proxy noch ein zentral betriebenes Datenbanksystem.
|
||||
Passwörter und Session-Cookies werden bei reinem HTTP unverschlüsselt
|
||||
übertragen. Der Stack darf deshalb nicht öffentlich erreichbar gemacht werden.
|
||||
|
||||
## Topologie
|
||||
|
||||
|
|
@ -29,9 +29,10 @@ deshalb nicht öffentlich erreichbar gemacht werden.
|
|||
| Express API | 3000 | nicht veröffentlicht (Server) / `127.0.0.1:3000` (Dev) | `GET /health` | `./data:/app/data` |
|
||||
| SQLite | Datei | – | Integritäts-/FK-Prüfung via Backup und Skript | `data/leistungsbilanz.db` |
|
||||
|
||||
Die Express-API wird im Server-Stack nicht mehr auf dem Host veröffentlicht
|
||||
(`expose` statt `ports`). Sie hat keine Authentifizierung; jeder, der sie
|
||||
erreicht, kann jedes Projekt lesen und ändern. Erreichbar ist sie nur aus dem
|
||||
Die Express-API wird im Server-Stack nicht auf dem Host veröffentlicht
|
||||
(`expose` statt `ports`). Alle fachlichen API-Routen verlangen eine aktive
|
||||
Session; Schreibzugriffe zusätzlich den Anwendungsheader
|
||||
`X-Leistungsbilanz-Request: 1`. Erreichbar ist die API nur aus dem
|
||||
Compose-Netz, praktisch also über den Next.js-Rewrite. Im Entwicklungs-Stack
|
||||
hängen beide Ports am Loopback und sind nicht aus dem LAN erreichbar.
|
||||
|
||||
|
|
@ -72,6 +73,26 @@ Verwendete Umgebungsvariablen:
|
|||
JSON-Loggers (`error`, `warn`, `info`, `verbose`, `debug`), Standard `info`.
|
||||
Setzbar über eine `.env`-Datei neben `compose.yaml` oder
|
||||
`LOG_LEVEL=verbose docker compose up`.
|
||||
- `AUTH_BOOTSTRAP_USERNAME` und `AUTH_BOOTSTRAP_PASSWORT` – erzeugen nur bei
|
||||
leerer Benutzertabelle den ersten Administrator; das Startpasswort hat
|
||||
mindestens 12 Zeichen.
|
||||
- `AUTH_SESSION_HOURS` – Inaktivitätsgrenze einer Session, Standard 12 Stunden.
|
||||
- `AUTH_SESSION_MAX_DAYS` – absolute Session-Laufzeit, Standard 7 Tage.
|
||||
- `COOKIE_SECURE` – hinter HTTPS `true`; beim direkten HTTP-Zugriff im LAN
|
||||
`false`, da Browser das Cookie sonst nicht speichern.
|
||||
|
||||
Vor dem ersten Start müssen mindestens die Bootstrap-Werte in einer lokalen,
|
||||
nicht versionierten `.env` neben `compose.yaml` gesetzt werden:
|
||||
|
||||
```dotenv
|
||||
AUTH_BOOTSTRAP_USERNAME=admin
|
||||
AUTH_BOOTSTRAP_PASSWORT=<eigenes Startpasswort mit mindestens 12 Zeichen>
|
||||
```
|
||||
|
||||
Nach dem ersten erfolgreichen Start werden diese Werte ignoriert, solange
|
||||
mindestens ein Benutzer existiert. Weitere Zugänge legt ein Administrator in
|
||||
**Config → Benutzer** an. Details stehen in
|
||||
[Anmeldung und Benutzerverwaltung](authentication.md).
|
||||
|
||||
Beim API-Start laufen zuerst die Migrationen und die Schemaprüfung
|
||||
(`scripts/run-migrations.js` und `scripts/db-verify-circuit-schema.js`, im
|
||||
|
|
@ -137,7 +158,8 @@ bleibt `sh` der Vordergrundprozess, und `SIGTERM` erreicht Node nie.
|
|||
Vor einer produktiven Installation werden mindestens benötigt:
|
||||
|
||||
- TLS-Termination und Reverse Proxy
|
||||
- Authentifizierung, Autorisierung und Benutzer-/Rollenmodell
|
||||
- zentraler Identitätsanbieter oder eine feinere Rechte-Matrix, falls die zwei
|
||||
festen Rollen später nicht ausreichen
|
||||
- definierte Secrets- und Konfigurationsverwaltung
|
||||
- persistenter, gesicherter Datenbankbetrieb
|
||||
- kontrollierter einmaliger Migrationsschritt vor dem API-Rollout
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue