227 lines
11 KiB
Markdown
227 lines
11 KiB
Markdown
# MPM – Architekturdokumentation
|
||
|
||
## 1. Leitentscheidung
|
||
|
||
Die Management-Plattform ist ein **Application Host und Gateway für Module** – nicht selbst die Fachanwendung.
|
||
|
||
```
|
||
Management-Plattform Module (ab Phase 3)
|
||
├── Identität & Login ├── eigene Oberfläche
|
||
├── Benutzer & Rollen ├── eigene Business-Logik
|
||
├── Rechte (plattformweit) ├── eigene API
|
||
├── Modul-Verwaltung ├── eigene Daten (eigenes Schema)
|
||
├── Routing / Gateway ├── eigene Migrationen
|
||
├── API & Administration └── optional eigene Modulrechte
|
||
└── Audit & Monitoring
|
||
```
|
||
|
||
Die Plattform darf **niemals** von einem einzelnen Modul abhängig sein. Wird ein Modul entfernt, bleiben Login, Benutzerverwaltung, Administration, Rechteverwaltung und alle anderen Module voll funktionsfähig.
|
||
|
||
## 2. Komponenten & Container-Layout
|
||
|
||
```
|
||
Internet
|
||
│
|
||
▼ HTTPS (Produktion; lokal :8080)
|
||
Docker Container "mpm-platform" (Benutzer: app, kein Root)
|
||
┌──────────────────────────────────────────────┐
|
||
│ Supervisor (Prozessmanager, Crash-Recovery) │
|
||
│ │ │
|
||
│ ├── nginx Reverse Proxy :8080 │
|
||
│ ├── platform-backend NestJS API 127.0.0.1:3000│
|
||
│ └── [ab Phase 3: Modul-Prozesse, │
|
||
│ z. B. Kalender 127.0.0.1:41001] │
|
||
└──────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
PostgreSQL "mpm-postgres" (eigener Container,
|
||
persistentes Volume "postgres-data")
|
||
```
|
||
|
||
Grundsätze:
|
||
|
||
- **Ein** Applikationscontainer; Module laufen als interne Prozesse darin (kein Docker-in-Docker, kein Docker-Socket).
|
||
- Interne Ports (z. B. 3000, 41001+) werden **nie** nach außen veröffentlicht; nur Nginx lauscht auf 8080.
|
||
- PostgreSQL liegt außerhalb des Applikationscontainers → Container-Neustarts/Updates zerstören keine Nutzdaten.
|
||
- Alle Prozesse laufen als unprivilegierter Benutzer `app`.
|
||
|
||
## 3. Prozessmodell
|
||
|
||
Supervisor verwaltet Start/Stop/Restart, Crash-Recovery und Logging aller Prozesse:
|
||
|
||
| Prozess | Adresse | Aufgabe |
|
||
|---|---|---|
|
||
| nginx | 0.0.0.0:8080 | Reverse Proxy, statisches Frontend, Security-Header |
|
||
| platform-backend | 127.0.0.1:3000 | Management-API `/api/v1` |
|
||
| Modul-Prozesse (ab Phase 3) | 127.0.0.1:41001+ | Fachanwendungen |
|
||
|
||
Stürzt ein Modulprozess ab, startet Supervisor ihn automatisch neu – die Management-Plattform läuft weiter.
|
||
|
||
## 4. Request-Flow
|
||
|
||
### Login
|
||
|
||
```
|
||
Browser ──POST /api/v1/auth/login──▶ Nginx ──▶ NestJS
|
||
│ Validierung (Zod) + Rate Limit + Lockout + Argon2id
|
||
│ Session in PostgreSQL anlegen (Token nur gehasht gespeichert)
|
||
◀── Set-Cookie: mpm_session (HttpOnly, SameSite=Lax)
|
||
Set-Cookie: mpm_csrf (lesbar für CSRF-Doppel-Submit)
|
||
```
|
||
|
||
### Authentifizierter Request
|
||
|
||
```
|
||
Browser ──▶ Nginx ──▶ SessionGuard (Session gültig? User aktiv?)
|
||
├── CsrfGuard (bei POST/PATCH/DELETE: X-CSRF-Token)
|
||
├── RolesGuard (RBAC: ADMIN/USER)
|
||
└── Controller/Service (Business-Logik)
|
||
```
|
||
|
||
### Modul-Routing (ab Phase 4, geplant)
|
||
|
||
```
|
||
Browser ──▶ Nginx (/slug) ──▶ Management-Gateway
|
||
├── User identifizieren (Session)
|
||
├── Permission Check (user_module_permissions)
|
||
├── DENIED → 403
|
||
└── ALLOWED → Modul-Gateway → Modulprozess
|
||
```
|
||
|
||
Ein Modul vertraut **niemals** allein auf die URL; die Plattform übergibt die Identität sicher an das Modul (Modul-API-Vertrag, Phase 6).
|
||
|
||
## 5. Datenmodell (Phase 1)
|
||
|
||
Alle Plattform-Tabellen liegen im Standard-Schema (ab Phase 3 Auslagerung in ein `management`-Schema und pro Modul ein eigenes Schema).
|
||
|
||
| Tabelle | Zweck |
|
||
|---|---|
|
||
| `roles` | Globale Rollen: `ADMIN`, `USER` |
|
||
| `users` | Benutzer mit Argon2id-Hash, Rollen-FK, Lockout-Feldern |
|
||
| `sessions` | Serverseitige Sessions (Token SHA-256-gehasht, CSRF-Token, Gleitende Verlängerung) |
|
||
| `audit_logs` | Zentrales Audit (LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT, …) |
|
||
| `schema_migrations` | Angewandte Migrationen (eigener Runner mit Advisory-Lock) |
|
||
|
||
Geplant (Phase 3+): `modules`, `user_module_permissions`, `module_settings`, `system_settings`, `api_clients`.
|
||
|
||
## 6. Authentifizierung & Sessions
|
||
|
||
- **Argon2id** (19 MiB, t=2, p=1 – OWASP-Empfehlung) für Passwort-Hashing; niemals Klartextpasswörter.
|
||
- **Serverseitige Sessions**: 256-Bit-Zufalls-Token im `HttpOnly`-Cookie; in der DB wird nur der SHA-256-Hash gespeichert. `Secure` (produktiv), `SameSite=Lax`, kurze Laufzeit (`SESSION_TTL_MINUTES`), gleitende Verlängerung.
|
||
- **CSRF-Schutz**: Doppel-Submit – Server speichert pro Session ein CSRF-Token; bei jedem zustandsändernden Request muss der Header `X-CSRF-Token` (konstantzeitvergleich) übereinstimmen.
|
||
- **Rate Limiting**: In-Memory-Fenster pro IP für Login (Standard: 10 Versuche / 5 Minuten).
|
||
- **Account Lockout**: Nach `LOGIN_MAX_ATTEMPTS` Fehlversuchen wird das Konto für `LOGIN_LOCKOUT_MINUTES` gesperrt.
|
||
- **Keine User-Enumeration**: Unbekannter Benutzer, inaktiver Benutzer und falsches Passwort liefern dieselbe generische 401-Meldung.
|
||
- **Audit-Log**: Jeder Login-Versuch (Erfolg/Misserfolg mit Grund), Logout.
|
||
|
||
## 7. Sicherheitsmaßnahmen (Phase 1)
|
||
|
||
- Security-Header via Helmet (Backend) und Nginx (Frontend): CSP, `X-Frame-Options: DENY`, `X-Content-Type-Options`, `Referrer-Policy`.
|
||
- Zod-Validierung **aller** Eingaben (serverseitig, clientseitige Validierung ist nur UX).
|
||
- SQL nur parametrisiert (`pg`), keine String-Konkatenation.
|
||
- Strukturierte Fehlermeldungen ohne Stack-Traces nach außen.
|
||
- Secrets ausschließlich über Umgebungsvariablen (`.env` ist gitignored).
|
||
- Interne Ports nicht veröffentlicht; PostgreSQL nur auf Loopback des Hosts gemappt.
|
||
- Unprivilegierter Container-Benutzer; kein Root in der Laufzeit.
|
||
|
||
Security Hardening (Penetrationstests, Dependency/Container-Scans, HSTS, Backup/Restore) ist gemäß Arbeitsplan Phase 10.
|
||
|
||
## 8. API-Übersicht (Phase 1)
|
||
|
||
| Methode | Pfad | Auth | Beschreibung |
|
||
|---|---|---|---|
|
||
| POST | `/api/v1/auth/login` | Public | Anmeldung, setzt Session-Cookies |
|
||
| POST | `/api/v1/auth/logout` | Session | Beendet die aktuelle Session |
|
||
| GET | `/api/v1/auth/me` | Session | Angemeldeter Benutzer (Id, Rolle, …) |
|
||
| GET | `/api/v1/health` | Public | Systemstatus (Backend, DB-Latenz, Version, Uptime) |
|
||
| GET | `/api/v1/users` | Admin | Alle Benutzer |
|
||
| POST | `/api/v1/users` | Admin | Benutzer anlegen (409 bei Duplikat) |
|
||
| GET | `/api/v1/users/:id` | Admin | Einzelner Benutzer |
|
||
| PATCH | `/api/v1/users/:id` | Admin | Bearbeiten / Aktivieren / Deaktivieren |
|
||
| PATCH | `/api/v1/users/:id/password` | Admin | Passwort zurücksetzen |
|
||
| DELETE | `/api/v1/users/:id` | Admin | Benutzer löschen |
|
||
| GET | `/api/v1/profile` | Session | Eigenes Profil |
|
||
| PATCH | `/api/v1/profile/password` | Session | Eigenes Passwort ändern |
|
||
| GET | `/api/v1/modules` | Admin | Alle Module |
|
||
| POST | `/api/v1/modules/install` | Admin | ZIP-Paket installieren (multipart, Feld `package`) |
|
||
| GET | `/api/v1/modules/:id` | Admin | Einzelnes Modul |
|
||
| POST | `/api/v1/modules/:id/start` | Admin | Modul starten |
|
||
| POST | `/api/v1/modules/:id/stop` | Admin | Modul stoppen |
|
||
| POST | `/api/v1/modules/:id/restart` | Admin | Modul neu starten |
|
||
| PATCH | `/api/v1/modules/:id/enabled` | Admin | Modul aktivieren/deaktivieren |
|
||
| GET | `/api/v1/modules/:id/health` | Admin | Healthcheck ausführen |
|
||
| DELETE | `/api/v1/modules/:id` | Admin | Modul entfernen |
|
||
|
||
OpenAPI/Swagger UI: `/api/docs` · Ab Phase 4: dynamisches Routing `/slug`.
|
||
|
||
### Schutzregeln der Benutzerverwaltung
|
||
|
||
- Keine Selbst-Deaktivierung und keine Selbst-Löschung (400)
|
||
- Der letzte aktive Administrator kann nicht herabgestuft, deaktiviert oder gelöscht werden (400)
|
||
- Deaktivierte Benutzer werden sofort von allen Sessions abgemeldet
|
||
- Passwort-Reset (Admin) meldet den Benutzer von allen Sessions ab
|
||
- Eigenes Passwort ändern meldet alle übrigen Sessions ab (aktuelle bleibt aktiv)
|
||
|
||
## 9. Modul-System (Phase 3)
|
||
|
||
### Manifest-Vertrag
|
||
|
||
Jedes Modul-Paket (ZIP, max. 10 MB) enthält ein `module.json`:
|
||
|
||
```json
|
||
{
|
||
"id": "calendar",
|
||
"name": "Kalender",
|
||
"version": "1.0.0",
|
||
"slug": "kalender-tool",
|
||
"description": "Kalenderverwaltung",
|
||
"author": "…",
|
||
"runtime": "node",
|
||
"entrypoint": "server.js",
|
||
"port": 41001,
|
||
"healthcheck": "/health",
|
||
"apiVersion": "v1"
|
||
}
|
||
```
|
||
|
||
Validierung per Zod: ID/Slug-Muster, SemVer, Port 41000–41999, kein Pfad-Traversal im Entrypoint, Runtime `node`, apiVersion `v1`.
|
||
|
||
### Lifecycle
|
||
|
||
```
|
||
INSTALLED → STARTING → RUNNING → STOPPING → STOPPED
|
||
↓ ↓
|
||
ERROR ERROR DISABLED (via Enable/Disable)
|
||
```
|
||
|
||
### Sicherheitsmaßnahmen
|
||
|
||
- **Zip-Slip-Schutz**: Alle ZIP-Einträge müssen im Zielverzeichnis bleiben
|
||
- **Minimale Prozess-ENV**: Module erhalten nur `PATH`, `NODE_ENV`, `PORT` – keine Plattform-Secrets
|
||
- **Spawn ohne Shell**: keine Command-Injection möglich
|
||
- **Eigene Log-Dateien** pro Modul (`/app/data/logs/module-<id>.log`)
|
||
- **Startup-Grace**: Healthcheck mit 10 Retries, bevor ein Modul als ERROR gilt
|
||
- **Isolation**: Ein abgestürztes Modul (ERROR) beeinträchtigt weder Plattform noch andere Module
|
||
- Persistente Volumes: Modul-Dateien und Logs überleben Container-Updates
|
||
|
||
Minimal-API je Modul (Phase 6): `GET /health`, `GET /api/manifest`, `GET /api/me`. Details zum Paketformat: [`modules/README.md`](../modules/README.md).
|
||
|
||
## 10. Architektur-Entscheidungen (ADR-Kurzform)
|
||
|
||
| # | Entscheidung | Begründung |
|
||
|---|---|---|
|
||
| 1 | Serverseitige Sessions statt reiner JWTs | Widerrufbar (Logout, Deaktivierung), kein Token-Diebstahl-Risiko, einfache Lockout-Logik; JWT/Access-Tokens für die Modul-Kommunikation ab Phase 6 |
|
||
| 2 | Opaque 256-Bit-Token, DB speichert nur SHA-256-Hash | DB-Leck kompromittiert keine Sessions |
|
||
| 3 | PostgreSQL außerhalb des Applikationscontainers | Persistenz bei Container-Updates, klare Trennung von Zustand und Compute |
|
||
| 4 | Supervisor statt systemd im Container | Container-Standard, verwaltet mehrere Prozesse ohne Root, Crash-Recovery |
|
||
| 5 | Nginx im Container als einziger öffentlicher Endpunkt | Zentrales Routing, Security-Header, interne Ports bleiben verborgen |
|
||
| 6 | `pg` + eigener Migration-Runner statt ORM | Wenig Abhängigkeiten, volle SQL-Kontrolle, Migrationen transaktionssicher mit Advisory-Lock |
|
||
| 7 | Zod statt class-validator | Ein Validierungs-Framework für Frontend und Backend, TypeScript-Inferenz |
|
||
| 8 | Monorepo mit unabhängigen Apps (kein Workspace-Tooling) | Einfache, cache-freundliche Docker-Builds, keine Tool-Lock-in |
|
||
|
||
## 11. Frontend-Architektur
|
||
|
||
- Schichten: `components/ui` (Design-System) → `components/layout` (App-Shell) → `features/*` (Fachseiten) → `pages` (Fehlerseiten).
|
||
- Datenzugriff ausschließlich über `lib/api-client` (CSRF-Header, 401-Behandlung) und TanStack Query.
|
||
- Routing: öffentliche Login-Seite, geschützter Bereich (`RequireAuth`), Admin-Bereich (`RequireAdmin`, RBAC im Frontend nur UX – erzwungen wird serverseitig).
|
||
- Responsiv: Sidebar wird auf Mobilgeräten zur Overlay-Navigation. |