Document authenticated deployment

This commit is contained in:
Julian Appel 2026-09-28 22:42:11 +02:00
parent 847f4e9493
commit 3670f02ccd
9 changed files with 97 additions and 20 deletions

7
.env.example Normal file
View file

@ -0,0 +1,7 @@
# Nur beim ersten Start mit leerer Benutzertabelle erforderlich.
AUTH_BOOTSTRAP_USERNAME=
AUTH_BOOTSTRAP_PASSWORT=
AUTH_SESSION_HOURS=12
AUTH_SESSION_MAX_DAYS=7
# Hinter HTTPS true, bei direktem HTTP-Zugriff im LAN false.
COOKIE_SECURE=false

3
.gitignore vendored
View file

@ -1,6 +1,9 @@
node_modules/ node_modules/
dist/ dist/
.next/ .next/
.env
.env.*
!.env.example
# Streuprodukt von tsc; drizzle-kit liest drizzle.config.ts. # Streuprodukt von tsc; drizzle-kit liest drizzle.config.ts.
/drizzle.config.js /drizzle.config.js
data/*.db data/*.db

View file

@ -42,7 +42,10 @@ It must support circuits, device rows, project devices, drag-and-drop restructur
`src/db/repositories/user.repository.ts`. Every `/api/*` route requires a `src/db/repositories/user.repository.ts`. Every `/api/*` route requires a
signed-in user (`src/server/index.ts`); `/api/users/*` additionally signed-in user (`src/server/index.ts`); `/api/users/*` additionally
requires `admin`. Frontend gate: `AuthProvider` + `LoginGate` requires `admin`. Frontend gate: `AuthProvider` + `LoginGate`
(`src/app/layout.tsx`). See `docs/authentication.md` for the full picture. (`src/app/layout.tsx`). Every unsafe API request also requires the explicit
`X-Leistungsbilanz-Request: 1` application header, including over plain HTTP.
Account settings are available to every signed-in user; user administration
remains admin-only. See `docs/authentication.md` for the full picture.
The supported runtime model is Circuit-First. The former Consumer UI, API, The supported runtime model is Circuit-First. The former Consumer UI, API,
tables and upgrade tooling are removed; do not reintroduce them. tables and upgrade tooling are removed; do not reintroduce them.

View file

@ -7,7 +7,9 @@ Gerätezeilen und wiederverwendbare Projektgeräte fachlich getrennt behandelt.
Das Projekt befindet sich in aktiver Entwicklung. Für SQLite stehen ein Das Projekt befindet sich in aktiver Entwicklung. Für SQLite stehen ein
Docker-Server-Stack mit gebautem Anwendungscode und ein separater Docker-Server-Stack mit gebautem Anwendungscode und ein separater
Entwicklungsstack bereit. Ein vollständig unterstütztes Produktionsdeployment Entwicklungsstack bereit. Ein vollständig unterstütztes Produktionsdeployment
und Mehrbenutzerbetrieb fehlen weiterhin. Revit-CSV-Konfiguration, Vorschau für mehrere gleichzeitig arbeitende Prozesse und ein vollständig unterstütztes
Produktionsdeployment fehlen weiterhin. Anmeldung und zwei feste Rollen sind
vorhanden. Revit-CSV-Konfiguration, Vorschau
und bestätigter Erstimport sind implementiert; die Editoroberfläche zur und bestätigter Erstimport sind implementiert; die Editoroberfläche zur
Objektzuordnung, Folgeimporte und Rückexport stehen noch aus. Die weitere Objektzuordnung, Folgeimporte und Rückexport stehen noch aus. Die weitere
Revit-Arbeit ist zurückgestellt und erfolgt später über den CSV-Export/-Import Revit-Arbeit ist zurückgestellt und erfolgt später über den CSV-Export/-Import
@ -60,6 +62,8 @@ Voraussetzungen:
```powershell ```powershell
git clone <repository-url> git clone <repository-url>
Set-Location leistungsbilanz-ts Set-Location leistungsbilanz-ts
$env:AUTH_BOOTSTRAP_USERNAME = "admin"
$env:AUTH_BOOTSTRAP_PASSWORT = "<eigenes Startpasswort mit mindestens 12 Zeichen>"
docker compose up --build --detach docker compose up --build --detach
``` ```
@ -68,9 +72,15 @@ Danach:
- Frontend: <http://localhost:3001> - Frontend: <http://localhost:3001>
- API-Healthcheck: <http://localhost:3001/health> (über den Next.js-Rewrite) - API-Healthcheck: <http://localhost:3001/health> (über den Next.js-Rewrite)
Die Express-API ist nicht mehr auf dem Host veröffentlicht. Sie hat keine Beim ersten Start erzeugen diese beiden Variablen genau einen Administrator,
Authentifizierung und ist nur innerhalb des Compose-Netzes erreichbar; der solange die Benutzertabelle noch leer ist. Danach werden sie ignoriert. Für
Zugriff läuft über den Rewrite im Web-Dienst. einen dauerhaften Serverbetrieb gehören die Werte in eine nicht versionierte
`.env` neben `compose.yaml`; Details stehen unter
[Anmeldung und Benutzerverwaltung](docs/authentication.md).
Die Express-API ist nicht auf dem Host veröffentlicht. Sämtliche fachlichen
API-Routen verlangen eine gültige Anmeldung; der Zugriff läuft über den
Rewrite im Web-Dienst.
Der API-Container führt ausstehende Migrationen und die Schemaprüfung beim Start Der API-Container führt ausstehende Migrationen und die Schemaprüfung beim Start
automatisch aus. Die SQLite-Datei liegt auf dem Host unter automatisch aus. Die SQLite-Datei liegt auf dem Host unter
@ -116,6 +126,8 @@ Voraussetzungen:
npm ci npm ci
npm run db:migrate npm run db:migrate
npm run db:verify:circuit-schema npm run db:verify:circuit-schema
$env:AUTH_BOOTSTRAP_USERNAME = "admin"
$env:AUTH_BOOTSTRAP_PASSWORT = "<eigenes Startpasswort mit mindestens 12 Zeichen>"
npm run dev:api npm run dev:api
``` ```
@ -162,6 +174,7 @@ vor der Baseline werden nicht unterstützt.
- [Gebäudestruktur und Raumflächen](docs/building-structure.md) - [Gebäudestruktur und Raumflächen](docs/building-structure.md)
- [Grobe Leistungsbilanz](docs/estimate-balance.md) - [Grobe Leistungsbilanz](docs/estimate-balance.md)
- [Leitungsberechnung](docs/cable-sizing-module.md) - [Leitungsberechnung](docs/cable-sizing-module.md)
- [Anmeldung und Benutzerverwaltung](docs/authentication.md)
- [Entwicklungs- und Contributor-Workflow](docs/development-workflow.md) - [Entwicklungs- und Contributor-Workflow](docs/development-workflow.md)
- [Bekannte Einschränkungen](docs/circuit-list-editor-known-limitations.md) - [Bekannte Einschränkungen](docs/circuit-list-editor-known-limitations.md)
- [Roadmap](docs/spec/07-implementation-phases-todo.md) - [Roadmap](docs/spec/07-implementation-phases-todo.md)

View file

@ -38,6 +38,8 @@ services:
AUTH_BOOTSTRAP_USERNAME: "${AUTH_BOOTSTRAP_USERNAME:-}" AUTH_BOOTSTRAP_USERNAME: "${AUTH_BOOTSTRAP_USERNAME:-}"
AUTH_BOOTSTRAP_PASSWORT: "${AUTH_BOOTSTRAP_PASSWORT:-}" AUTH_BOOTSTRAP_PASSWORT: "${AUTH_BOOTSTRAP_PASSWORT:-}"
AUTH_SESSION_HOURS: "${AUTH_SESSION_HOURS:-12}" AUTH_SESSION_HOURS: "${AUTH_SESSION_HOURS:-12}"
AUTH_SESSION_MAX_DAYS: "${AUTH_SESSION_MAX_DAYS:-7}"
COOKIE_SECURE: "${COOKIE_SECURE:-false}"
# Nur auf dem Loopback: die API darf nicht aus dem LAN erreichbar sein. # Nur auf dem Loopback: die API darf nicht aus dem LAN erreichbar sein.
ports: ports:
- "127.0.0.1:3000:3000" - "127.0.0.1:3000:3000"

View file

@ -47,7 +47,7 @@ services:
LOG_LEVEL: "${LOG_LEVEL:-info}" LOG_LEVEL: "${LOG_LEVEL:-info}"
# AUTH_BOOTSTRAP_* legen nur den allerersten Admin an (solange die # AUTH_BOOTSTRAP_* legen nur den allerersten Admin an (solange die
# users-Tabelle leer ist) und gehoeren in eine lokale, nicht versionierte # users-Tabelle leer ist) und gehoeren in eine lokale, nicht versionierte
# .env - siehe docs/AUTH.md. # .env - siehe docs/authentication.md.
AUTH_BOOTSTRAP_USERNAME: "${AUTH_BOOTSTRAP_USERNAME:-}" AUTH_BOOTSTRAP_USERNAME: "${AUTH_BOOTSTRAP_USERNAME:-}"
AUTH_BOOTSTRAP_PASSWORT: "${AUTH_BOOTSTRAP_PASSWORT:-}" AUTH_BOOTSTRAP_PASSWORT: "${AUTH_BOOTSTRAP_PASSWORT:-}"
AUTH_SESSION_HOURS: "${AUTH_SESSION_HOURS:-12}" AUTH_SESSION_HOURS: "${AUTH_SESSION_HOURS:-12}"

View file

@ -7,10 +7,9 @@ Zugang wird von einem Administrator über **Config → Benutzer** angelegt.
Es gibt genau zwei Rollen (`src/shared/constants/user-roles.ts`): Es gibt genau zwei Rollen (`src/shared/constants/user-roles.ts`):
- **admin** – wie `user`, zusätzlich Zugriff auf den Config-Bereich - **admin** – wie `user`, zusätzlich Zugriff auf **Config → Benutzer**
(Benutzerverwaltung, eigenes Konto)
- **user** – volle Nutzung der Leistungsbilanz (Projekte, Stromkreislisten, - **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 Es gibt (bewusst, siehe unten) keine konfigurierbare Rechte-Matrix und keine
Verknüpfung mit einer Mitarbeiter-/Personen-Stammdatentabelle. 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` - **Frontend:** `AuthProvider` lädt `/api/auth/me`; `LoginGate`
(`src/app/layout.tsx`) zeigt Login- bzw. Passwortformular, solange nicht (`src/app/layout.tsx`) zeigt Login- bzw. Passwortformular, solange nicht
angemeldet bzw. `mustChangePassword` gesetzt ist, sonst die App. angemeldet bzw. `mustChangePassword` gesetzt ist, sonst die App.
**Konto** ist für jede angemeldete Person erreichbar; **Benutzer** nur für
Administratoren.
## Schutzmechanismen ## 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 (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 | | 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 | | 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 | 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 | 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 ü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 | | 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) | | 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 | | 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()` | | 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 | | 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 | | 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 gehören in eine lokale, nicht versionierte `.env` auf dem Deploy-Host, nicht
in `compose.yaml` selbst. 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) ## Bewusst nicht umgesetzt (Stand jetzt)
- Konfigurierbare Rechte-Matrix statt fixer `admin`/`user`-Rollen - Konfigurierbare Rechte-Matrix statt fixer `admin`/`user`-Rollen

View file

@ -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 weiter; beim gebauten Stand wird das Ziel zur Build-Zeit festgelegt. Die
Datenbank liegt über einen Host-Mount außerhalb des Containers. 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 Neue IDs für Stromkreise, Gerätezeilen, Gruppen und Verteilerkomponenten erzeugt
der Editor über `src/frontend/utils/client-id.ts` (`createClientId`). Die der Editor über `src/frontend/utils/client-id.ts` (`createClientId`). Die
Hilfsfunktion nutzt `crypto.randomUUID()`, sofern verfügbar, andernfalls 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, - `src/frontend/components/circuit-grid-*.ts` – reine Grid-Projektion,
Zellbesitz, Einfügen und Sicherheitsregeln Zellbesitz, Einfügen und Sicherheitsregeln
- `src/frontend/utils/api.ts` – typisierte Frontend-API-Aufrufe - `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/server/index.ts` und `src/server/routes/` – API-Komposition
- `src/domain/services/` – fachliche Command- und Synchronisierungsregeln - `src/domain/services/` – fachliche Command- und Synchronisierungsregeln
- `src/server/composition/` – Verdrahtung fachlicher Services mit konkreten - `src/server/composition/` – Verdrahtung fachlicher Services mit konkreten

View file

@ -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 ohne Benutzeraktivität. Dieser Stack gehört deshalb nur auf einen
Entwicklungsrechner. Entwicklungsrechner.
Beide Stacks enthalten weder TLS, Authentifizierung, einen vorgeschalteten Beide Stacks enthalten Anmeldung mit festen Rollen, aber weder TLS, einen
Reverse Proxy noch ein zentral betriebenes Datenbanksystem. Die oben genannten vorgeschalteten Reverse Proxy noch ein zentral betriebenes Datenbanksystem.
Container-Schutzmaßnahmen ersetzen diese Funktionen nicht. Der Stack darf Passwörter und Session-Cookies werden bei reinem HTTP unverschlüsselt
deshalb nicht öffentlich erreichbar gemacht werden. übertragen. Der Stack darf deshalb nicht öffentlich erreichbar gemacht werden.
## Topologie ## 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` | | 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` | | 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 Die Express-API wird im Server-Stack nicht auf dem Host veröffentlicht
(`expose` statt `ports`). Sie hat keine Authentifizierung; jeder, der sie (`expose` statt `ports`). Alle fachlichen API-Routen verlangen eine aktive
erreicht, kann jedes Projekt lesen und ändern. Erreichbar ist sie nur aus dem 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 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. 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`. JSON-Loggers (`error`, `warn`, `info`, `verbose`, `debug`), Standard `info`.
Setzbar über eine `.env`-Datei neben `compose.yaml` oder Setzbar über eine `.env`-Datei neben `compose.yaml` oder
`LOG_LEVEL=verbose docker compose up`. `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 Beim API-Start laufen zuerst die Migrationen und die Schemaprüfung
(`scripts/run-migrations.js` und `scripts/db-verify-circuit-schema.js`, im (`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: Vor einer produktiven Installation werden mindestens benötigt:
- TLS-Termination und Reverse Proxy - 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 - definierte Secrets- und Konfigurationsverwaltung
- persistenter, gesicherter Datenbankbetrieb - persistenter, gesicherter Datenbankbetrieb
- kontrollierter einmaliger Migrationsschritt vor dem API-Rollout - kontrollierter einmaliger Migrationsschritt vor dem API-Rollout