Files
mpm/docs/ARCHITECTURE.md

11 KiB
Raw Blame History

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:

{
  "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.

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.