327 lines
13 KiB
Markdown
327 lines
13 KiB
Markdown
|
|
# Auth & Session – Designprinzipien (Extraktion)
|
|||
|
|
|
|||
|
|
**Status:** Analyse / Arbeitspapier
|
|||
|
|
**Stand:** 2026-07-04
|
|||
|
|
**Geltungsbereich:** Authentifizierung, Session-Management, rollenbasierte API-Zugriffe — kein Mandanten-/SSO-System, keine Zahlungs-Auth
|
|||
|
|
|
|||
|
|
**Serie:** Designprinzipien für Produktfamilie · Dokument 5 von n
|
|||
|
|
**Vorgänger:** [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md)
|
|||
|
|
|
|||
|
|
**Kernkomponenten:**
|
|||
|
|
|
|||
|
|
| Bereich | Pfade |
|
|||
|
|
|---------|-------|
|
|||
|
|
| Auth-Kern | `backend/auth.py` |
|
|||
|
|
| Auth-Endpoints | `backend/routers/auth.py` |
|
|||
|
|
| Profile | `backend/routers/profiles.py` |
|
|||
|
|
| Frontend | `frontend/src/context/AuthContext.jsx`, `ProfileContext.jsx`, `utils/api.js` |
|
|||
|
|
| DB | `profiles`, `sessions` |
|
|||
|
|
| Architektur-Regeln | `.claude/rules/ARCHITECTURE.md`, `CLAUDE.md` § Auth |
|
|||
|
|
| Vision (nicht implementiert) | `CENTRAL_SUBSCRIPTION_SYSTEM.md` (SSO/JWT) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Modul
|
|||
|
|
|
|||
|
|
**Auth & Session**
|
|||
|
|
|
|||
|
|
Server-seitige, token-basierte Authentifizierung mit FastAPI-Dependencies — **Identität und Rolle**, getrennt von Feature-Entitlements und fachlicher Logik.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Fachliche Verantwortung
|
|||
|
|
|
|||
|
|
Das Modul übernimmt:
|
|||
|
|
|
|||
|
|
1. **Identität** — Wer ist eingeloggt? (`profiles` + Passwort/bcrypt)
|
|||
|
|
2. **Session** — Opaque Token in `sessions`, Ablaufzeit, Logout
|
|||
|
|
3. **API-Gate** — `require_auth`, `require_admin`, `require_auth_flexible`
|
|||
|
|
4. **Passwort-Lifecycle** — Hash, Verify, Reset, Registrierung, E-Mail-Verifizierung
|
|||
|
|
5. **Rollen** — `profiles.role`: `user` \| `admin` (grobbinsenartig)
|
|||
|
|
|
|||
|
|
Es übernimmt **nicht**:
|
|||
|
|
|
|||
|
|
- Feature-Limits / Tier (→ Feature & Entitlement System, gleiche `auth.py`-Datei aber logisch getrennt)
|
|||
|
|
- Mandanten-Isolation / Org-Workspaces
|
|||
|
|
- OAuth/SSO/JWT (nur Vision)
|
|||
|
|
- Authorization auf Datensatzebene (Row-Level Security)
|
|||
|
|
|
|||
|
|
### Was Mitai **ist** vs. **nicht ist**
|
|||
|
|
|
|||
|
|
| Mitai | Produktfamilien-Muster |
|
|||
|
|
|-------|------------------------|
|
|||
|
|
| 1 Login = 1 Profil (E-Mail) | ✅ Account-Modell |
|
|||
|
|
| Historisch Multi-Profil auf einer Instanz | ⚠️ Legacy (`/profiles`, `X-Profile-Id`) |
|
|||
|
|
| Self-hosted Einzelinstanz | ✅ Kein Multi-Tenant-SaaS |
|
|||
|
|
| Session-Token in DB | ✅ Server-side Session Store |
|
|||
|
|
| Zentrale Jinkendo-Auth (Vision) | ❌ nicht gebaut |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Administrierte vs. code-definierte Konfiguration
|
|||
|
|
|
|||
|
|
| Konfiguration | Speicherort | Administrierbar? |
|
|||
|
|
|---------------|-------------|------------------|
|
|||
|
|
| Nutzer-Stammdaten, Rolle | `profiles` | Admin (User-Verwaltung) / Self-Service |
|
|||
|
|
| Session-Laufzeit | `profiles.session_days` (Default 30) | Profil/Admin |
|
|||
|
|
| Passwort-Hash | `profiles.pin_hash` | Nutzer (change pin) |
|
|||
|
|
| E-Mail-Verifizierung | `email_verified`, Token-Felder | System |
|
|||
|
|
| Trial-Ende | `trial_ends_at` | System bei Registrierung |
|
|||
|
|
| SMTP | Env (`SMTP_*`, `APP_URL`) | Deploy |
|
|||
|
|
| Rate Limits Login/Register | Code (`5/min`, `3/hour`) | Code |
|
|||
|
|
|
|||
|
|
**Hardcodiert:** bcrypt, Token-Länge (`secrets.token_urlsafe(32)`), Rollen-Enum (`user`/`admin`), Header-Name `X-Auth-Token`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Session- und Auth-Flow
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Login (email + password)
|
|||
|
|
→ verify_pin (bcrypt | legacy SHA256)
|
|||
|
|
→ optional bcrypt upgrade
|
|||
|
|
→ INSERT sessions (token, profile_id, expires_at)
|
|||
|
|
→ Client: localStorage bodytrack_token
|
|||
|
|
|
|||
|
|
Request
|
|||
|
|
→ Header X-Auth-Token (api.js / AuthContext)
|
|||
|
|
→ get_session(token) JOIN profiles
|
|||
|
|
→ require_auth → session dict (profile_id, role, …)
|
|||
|
|
|
|||
|
|
Logout
|
|||
|
|
→ DELETE sessions WHERE token=…
|
|||
|
|
→ Client: localStorage clear
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Sonderfall:** `require_auth_flexible` — Token via Header **oder** Query `ssetoken` (SSE, `<img>`, Downloads).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Rollen
|
|||
|
|
|
|||
|
|
| Rolle | Mechanismus | Typische Rechte |
|
|||
|
|
|-------|-------------|-----------------|
|
|||
|
|
| **user** | `profiles.role = 'user'` | Eigene Daten, Features nach Tier |
|
|||
|
|
| **admin** | `require_admin` | Admin-Shell, Prompts, User, System |
|
|||
|
|
|
|||
|
|
Kein feingranulares RBAC (keine Permission-Matrix). Admin ist Binär-Schalter.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Designprinzipien
|
|||
|
|
|
|||
|
|
### 1. FastAPI-Dependencies als Auth-Gate
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Jeder geschützte Endpoint nutzt `session: dict = Depends(require_auth)` als **separaten** Parameter — nie in `Header()` eingebettet. |
|
|||
|
|
| **Begründung** | Verhindert ungeschützte Endpoints durch falsche Parameter-Signatur. |
|
|||
|
|
| **Quelle** | `CLAUDE.md` § Kritische Regeln; `auth.py` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Nicht linter-erzwungen; Legacy-Endpoints existieren. |
|
|||
|
|
|
|||
|
|
### 2. Server-side opaque Sessions
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Token ist zufällig, in DB gespeichert; Validierung über `sessions` + Ablauf — kein JWT mit Client-Claims. |
|
|||
|
|
| **Begründung** | Revocation (Logout), kein Trust in Client-Payload; einfaches Modell für Self-Hosted. |
|
|||
|
|
| **Quelle** | `sessions` Tabelle; `make_token()`, `get_session()` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** (Single-App, Self-Hosted) |
|
|||
|
|
| **Einschränkung** | Skalierung multi-node braucht shared session store; SSO-Familie braucht anderes Modell. |
|
|||
|
|
|
|||
|
|
### 3. profile_id aus Session, nicht aus Client
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Autoritative Identität für neue Endpoints: `session['profile_id']` — Client darf Profil nicht wählen. |
|
|||
|
|
| **Begründung** | Verhindert IDOR (Zugriff auf fremde Profile). |
|
|||
|
|
| **Quelle** | `routers/goals.py`, `routers/prompts.py`; Architektur-Intent in `CLAUDE.md` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Legacy `get_pid(x_profile_id)` akzeptiert `X-Profile-Id` **ohne** Session-Abgleich — siehe Nicht übernehmen. |
|
|||
|
|
|
|||
|
|
### 4. bcrypt mit Legacy-Migration
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Passwörter mit bcrypt; SHA256-Legacy beim Login erkannt und transparent auf bcrypt upgraded. |
|
|||
|
|
| **Begründung** | Kein Big-Bang-Migration; sichere Hashes ohne Nutzer-Zwangs-Reset. |
|
|||
|
|
| **Quelle** | `verify_pin()`, Login in `routers/auth.py` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Upgrade nur bei erfolgreichem Login. |
|
|||
|
|
|
|||
|
|
### 5. Rate Limiting auf Auth-Endpoints
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Login, Register, Forgot-Password, Resend-Verification mit `slowapi`-Limits (IP-basiert). |
|
|||
|
|
| **Begründung** | Brute-Force- und Abuse-Schutz. |
|
|||
|
|
| **Quelle** | `routers/auth.py` (`5/minute`, `3/hour`); `main.py` Limiter |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | IP-only; kein account-based lockout. |
|
|||
|
|
|
|||
|
|
### 6. Keine E-Mail-Enumeration bei sensiblen Flows
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Forgot-Password und Resend-Verification liefern generische Erfolgsmeldung, auch wenn E-Mail unbekannt. |
|
|||
|
|
| **Begründung** | Privacy; erschwert Account-Scraping. |
|
|||
|
|
| **Quelle** | `password_reset_request`, `resend_verification` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Register sagt „E-Mail bereits registriert“ (Enumeration möglich). |
|
|||
|
|
|
|||
|
|
### 7. E-Mail-Verifizierung vor voller Nutzung
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Self-Register setzt `email_verified=FALSE`; Verify-Endpoint aktiviert + Auto-Login-Session. |
|
|||
|
|
| **Begründung** | Valide Kontaktadresse; Spam-Reduktion. |
|
|||
|
|
| **Quelle** | `register`, `verify_email` in `routers/auth.py` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Nicht überall im Backend erzwungen (Login ohne verified check?). |
|
|||
|
|
|
|||
|
|
### 8. Flexible Auth für technische Clients
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | `require_auth_flexible`: gleiche Session-Validierung via Header oder `?ssetoken=` für SSE/Bilder. |
|
|||
|
|
| **Begründung** | Browser-APIs ohne Custom Headers. |
|
|||
|
|
| **Quelle** | `auth.py`; Prompt SSE `/execute-stream` |
|
|||
|
|
| **Tragfähigkeit** | **mittel–hoch** |
|
|||
|
|
| **Einschränkung** | Token in URL kann in Logs/Referrer leaken — kurze Sessions / HTTPS Pflicht. |
|
|||
|
|
|
|||
|
|
### 9. Zentraler API-Client mit Token-Injektion
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Frontend: `api.js` injiziert `X-Auth-Token` automatisch — kein scattered `fetch` ohne Auth. |
|
|||
|
|
| **Begründung** | Konsistenz; eine Stelle für Token-Handling. |
|
|||
|
|
| **Quelle** | `utils/api.js` → `hdrs()`; `getToken()` aus AuthContext |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Einzelne Komponenten umgehen noch `api.js` (SettingsPage, EmailSettings). |
|
|||
|
|
|
|||
|
|
### 10. Auth getrennt von Authorization (Features)
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | `require_auth` = identifiziert; `check_feature_access` = berechtigt für Aktion — nacheinander im Router. |
|
|||
|
|
| **Begründung** | Klare Schichten; Auth-Modul nicht mit Tier-Logik vermischen (auch wenn Datei `auth.py` beides enthält). |
|
|||
|
|
| **Quelle** | `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md`; Router-Muster |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Legacy Profil-Flags `ai_enabled`, `export_enabled` parallel zum Feature-System. |
|
|||
|
|
|
|||
|
|
### 11. Admin-Gate im Frontend und Backend
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | Backend: `require_admin`; Frontend: `RequireAdmin` + `isAdmin` aus Session-Rolle. |
|
|||
|
|
| **Begründung** | UX-Navigation + API-Sicherheit (Frontend allein reicht nicht). |
|
|||
|
|
| **Quelle** | `RequireAdmin.jsx`; `require_admin()` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | Einzelne Routen (Workflow-Editor) ohne Frontend-Admin-Gate. |
|
|||
|
|
|
|||
|
|
### 12. Session-Kontext im Frontend
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Prinzip** | `AuthProvider` hält `{ token, profile_id, role, profile }`; App setzt `setProfileId(session.profile_id)` für API. |
|
|||
|
|
| **Begründung** | Single React-Tree für Login-State; Re-Validate via `/auth/me` beim Start. |
|
|||
|
|
| **Quelle** | `AuthContext.jsx`; `App.jsx` |
|
|||
|
|
| **Tragfähigkeit** | **hoch** |
|
|||
|
|
| **Einschränkung** | `ProfileContext` lädt alle Profile — Multi-Profil-UX Rest; Session-Profil ist Kanon. |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Nicht übernehmen
|
|||
|
|
|
|||
|
|
1. **`get_pid(X-Profile-Id)` ohne Session-Bindung** — Client kann fremde `profile_id` senden; IDOR-Risiko. Kanon: immer `session['profile_id']` oder explizite Admin-Impersonation mit Audit.
|
|||
|
|
|
|||
|
|
2. **Profile-CRUD nur mit `require_auth`** — `/profiles` listet alle Nutzer für jeden Authentifizierten (Kommentar „admin“, kein `require_admin`). Für Familien-Architektur: strikte Admin-Gates.
|
|||
|
|
|
|||
|
|
3. **Dual-System Profil-Flags vs. Features** — `ai_enabled`, `export_enabled`, `ai_limit_day` in Session-Query neben v9c Feature-Registry.
|
|||
|
|
|
|||
|
|
4. **localStorage-Key-Inkonsistenz** — `bodytrack_token` vs. `mitai-jinkendo_active_profile` (historischer App-Name).
|
|||
|
|
|
|||
|
|
5. **Direktes `fetch` ohne `api.js`** — umgeht Token-/Error-Konvention.
|
|||
|
|
|
|||
|
|
6. **Reset-Token in `sessions`-Tabelle** — `reset_{token}` mischt Session-Typen in einer Tabelle; OK für MVP, für Familie: getrennte Token-Typen/Tabellen.
|
|||
|
|
|
|||
|
|
7. **Kein JWT/SSO trotz Produktfamilien-Vision** — `CENTRAL_SUBSCRIPTION_SYSTEM.md` beschreibt `auth.jinkendo.de` — Mitai-Implementierung ist **nicht** das Zielbild für Cross-App-SSO.
|
|||
|
|
|
|||
|
|
8. **Multi-Profil-Haushalt ohne klares Modell** — Legacy Multi-Profile auf einer Instanz vs. 1 Account = 1 Profil; für neue Apps Modell explizit wählen.
|
|||
|
|
|
|||
|
|
9. **Role als einziges RBAC** — reicht für Admin/User, nicht für feingranulare Permissions.
|
|||
|
|
|
|||
|
|
10. **Session-Query mit veralteten Profil-Spalten** — `get_session` SELECT enthält Legacy-Felder statt nur Identität + Rolle.
|
|||
|
|
|
|||
|
|
11. **Fehlende erzwungene E-Mail-Verified-Prüfung** — Registrierung setzt Flag, Login prüft es nicht offensichtlich.
|
|||
|
|
|
|||
|
|
12. **Debug-Print in Auth-Modul** — `print("[AUTH.PY] Module loaded…")` in Produktionscode.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Abgrenzung zu anderen Serien-Dokumenten
|
|||
|
|
|
|||
|
|
| Thema | Dokument |
|
|||
|
|
|-------|----------|
|
|||
|
|
| Tier, Limits, Quotas | [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) |
|
|||
|
|
| Zentrale SSO/Abo-Vision | [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md) |
|
|||
|
|
| API-First / Router | `ARCHITECTURE.md` §1 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Modul-Inventar (Ist-Stand)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
backend/
|
|||
|
|
├── auth.py # Session, require_*, Feature-Access (v9c)
|
|||
|
|
└── routers/
|
|||
|
|
├── auth.py # login, logout, register, verify, reset
|
|||
|
|
└── profiles.py # CRUD, get_pid (Legacy)
|
|||
|
|
|
|||
|
|
frontend/src/
|
|||
|
|
├── context/AuthContext.jsx
|
|||
|
|
├── context/ProfileContext.jsx
|
|||
|
|
├── layouts/RequireAdmin.jsx
|
|||
|
|
└── utils/api.js # Token-Injektion
|
|||
|
|
|
|||
|
|
DB:
|
|||
|
|
├── profiles # Identität, Rolle, Hash, Tier, Trial
|
|||
|
|
└── sessions # token → profile_id, expires_at
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Endpoints (Auswahl):**
|
|||
|
|
|
|||
|
|
| Endpoint | Auth |
|
|||
|
|
|----------|------|
|
|||
|
|
| `POST /api/auth/login` | Public + Rate limit |
|
|||
|
|
| `POST /api/auth/logout` | Token optional |
|
|||
|
|
| `GET /api/auth/me` | require_auth |
|
|||
|
|
| `POST /api/auth/register` | Public + Rate limit |
|
|||
|
|
| `GET /api/auth/verify/{token}` | Public |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Verwandte Dokumentation
|
|||
|
|
|
|||
|
|
- Feature-Entitlements: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)
|
|||
|
|
- Architektur-Regeln Auth: `CLAUDE.md`, `.claude/rules/ARCHITECTURE.md`
|
|||
|
|
- GUI Admin-Guard: `docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md`
|
|||
|
|
- SSO-Vision: [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Geplante Folgedokumente (Serie)
|
|||
|
|
|
|||
|
|
| # | Modul | Status |
|
|||
|
|
|---|-------|--------|
|
|||
|
|
| 1 | Prompt Engine | ✅ |
|
|||
|
|
| 2 | Data Layer | ✅ |
|
|||
|
|
| 3 | Feature & Entitlement | ✅ |
|
|||
|
|
| 4 | Registry-/Plugin-Muster | ✅ |
|
|||
|
|
| 5 | Auth & Session | ✅ dieses Dokument |
|
|||
|
|
| 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` |
|
|||
|
|
| 7 | Dashboard Widgets | ✅ |
|
|||
|
|
| 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` |
|
|||
|
|
| 9 | Migration & Deploy | ✅ |
|