shinkan-jinkendo/docs/compliance-implementation.md
Lars 28ca64b5b4
Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 26s
feat(compliance): P-12 sessionStorage-Bereinigung bei Logout (0.8.68)
Sicherheit P-12 (MITT-05): logout() entfernt alle sj_coach_*-Schlüssel
aus sessionStorage gezielt per Präfix-Löschung. Fremde Schlüssel
(Browser-Extensions etc.) bleiben erhalten. Verhindert Datenleak bei
Nutzerwechsel im selben Tab (geteilter Rechner).

- AuthContext.jsx: Präfix-Schleife in logout()
- tests/dev-smoke-test.spec.js: Playwright-Test P-12 (injects/checks 3
  sj_coach_*-Schlüssel + 1 Fremd-Schlüssel; prüft selektive Löschung)

Compliance-Dokumentation:
- docs/compliance-implementation.md: P-12 , Version 0.8.68
- docs/compliance-package-register.md: kanonisches Paketregister (neu)
- docs/compliance-roadmap.md: lebende Steuerungs-Roadmap (neu)
- docs/compliance-audit.md: §20 Paket-ID-Stabilitätsregel

version: 0.8.68 (backend + frontend)
module: auth 1.2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:08:28 +02:00

259 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Compliance-Implementierung Umsetzungsbericht
**Erstellt:** 2026-05-09
**Zuletzt aktualisiert:** 2026-05-10
**Audit-Basis:** `docs/compliance-audit.md`
**App-Version nach Umsetzung:** 0.8.68
---
## Freigegebene Pakete und Umsetzungsstatus
### P-03 Papierkorb-Retention-Job aktivieren ✅
**Status:** Umgesetzt
**Betroffene Dateien:**
- `docker-compose.yml` neuer Service `retention-cron`
**Technische Änderung:**
Neuer Docker-Service `retention-cron` nutzt dasselbe Backend-Image und führt `scripts/media_retention_job.py` täglich um 03:00 Uhr aus. Der Service startet beim ersten Hochfahren sofort und schläft bis zum nächsten 3 AM (Python-basierter Loop ohne externe Cron-Abhängigkeit). Zugriff auf DB und Media-Volume identisch zur Backend-Konfiguration.
**Tests:** Keine automatisierten Tests möglich (Runtime-Verhalten); operativ über Container-Logs (`docker logs shinkan-retention-cron`) überprüfbar.
---
### P-03b Retention-Zeiten mit Löschkonzept abgleichen ✅
**Status:** Umgesetzt (2026-05-10)
**Betroffene Dateien:**
- `backend/media_lifecycle.py:24` Default `HIDDEN_TO_PURGE_DAYS`
- `docker-compose.yml` explizite Env-Variable im `retention-cron`-Service
**Befund des Re-Audits:**
Das fachliche Löschkonzept (Audit §6.4) sieht 30 + 30 Tage vor:
- Stufe 1 → Stufe 2 (Soft → Hidden): 30 Tage ✓ (war bereits korrekt)
- Stufe 2 → Purge (Hidden → gelöscht): **weitere 30 Tage** → Code-Default war 90 Tage ❌
**Technische Änderung:**
```python
# media_lifecycle.py Zeile 24 (vorher "90", jetzt "30"):
HIDDEN_TO_PURGE_DAYS = max(1, int(os.getenv("MEDIA_TRASH_HIDDEN_TO_PURGE_DAYS", "30")))
```
`docker-compose.yml` enthält jetzt explizit `MEDIA_TRASH_HIDDEN_TO_PURGE_DAYS: "${...:-30}"` mit Kommentar, der das 30+30-Konzept dokumentiert. Der Wert kann per Env-Variable in einzelnen Deployments überschrieben werden.
---
### P-04 Copyright-Pflicht bei Archiv-Promotion vereinheitlichen ✅
**Status:** Umgesetzt; Tests nachgehärtet (2026-05-10)
**Betroffene Dateien:**
- `backend/routers/media_assets.py` `patch_media_asset()` und `bulk_media_patch()`
- `backend/tests/test_media_assets_copyright_promotion.py`
**Technische Änderung:**
Beide Endpoints prüfen, ob `copyright_notice` vorhanden ist, wenn `visibility` auf `club` oder `official` gewechselt wird. Priorität: Wert aus dem Request-Body > bestehender Wert in der DB. Ist beides leer, wird HTTP 400 zurückgegeben.
Fehlermeldung: `"Für Vereins- oder offizielle Medien ist eine Urheberrechtsangabe (copyright_notice) Pflicht."` (Umlaut korrekt, 2026-05-10 korrigiert)
**Tests:** 7 Tests, alle grün:
- Promotion zu `club` ohne Copyright → **400** (exakt)
- Promotion zu `official` ohne Copyright → **400** (exakt)
- Promotion zu `club` mit Copyright im Body → **200** + Payload-Prüfung (gehärtet)
- Promotion zu `club`, Copyright bereits auf Asset → **200** + Payload-Prüfung (gehärtet)
- Kein Visibility-Wechsel → keine Copyright-Prüfung → **200** (exakt)
- Bulk: ohne Copyright → in `failed`, `updated_count == 0` (exakt)
- Bulk: mit Copyright → in `updated`, `updated_count == 1` (exakt)
---
### P-05 Passwort-Mindestlänge angleichen ⚠️ (Teil 1 von 2 Re-Audit-Auflage offen)
**Status:** Teilweise umgesetzt
**Betroffene Dateien (initialer Fix 2026-05-09):**
- `backend/routers/auth.py:101` `PUT /api/auth/pin`: `< 4``< 8`
- `frontend/src/pages/LoginPage.jsx:175` `minLength="6"``minLength="8"`
- `frontend/src/pages/AccountSettingsPage.jsx:403` `minLength={4}``minLength={8}`
**Verbleibende Lücke identifiziert im Re-Audit 2026-05-09:** `POST /api/auth/reset-password` hatte kein Mindestlängen-Limit → siehe P-05b.
---
### P-05b reset-password Mindestlänge 8 Zeichen ✅
**Status:** Umgesetzt (2026-05-10)
**Betroffene Dateien:**
- `backend/models.py:29` `PasswordResetConfirm.new_password`
- `backend/tests/test_auth_password_reset_minlength.py` 7 neue Tests
**Technische Änderung:**
```python
# models.py (vorher):
class PasswordResetConfirm(BaseModel):
token: str
new_password: str
# models.py (nachher):
class PasswordResetConfirm(BaseModel):
token: str
new_password: str = Field(min_length=8, max_length=128)
```
FastAPI lehnt Requests mit `new_password < 8 Zeichen` nun mit HTTP **422** (Pydantic Validation Error) ab, bevor der Endpoint-Handler ausgeführt wird. Kein DB-Zugriff erfolgt für unvalide Requests.
**Tests:** 7 Tests, alle grün:
- Leer-String → 422
- 1-Zeichen-Passwort → 422
- 7-Zeichen-Passwort (`"1234567"`) → 422
- Exakt 8 Zeichen (`"12345678"`) → **200**
- Langes Passwort → **200**
- Fehlendes `new_password`-Feld → 422
- Gültiges Passwort, ungültiger Token → 400
**P-05 vollständig geschlossen?** Ja — alle passwortverarbeitenden Backend-Endpoints erzwingen jetzt Mindestlänge 8:
| Endpoint | Mindestlänge | Mechanismus |
|---|---|---|
| `POST /api/auth/register` | 8 | `if len(password) < 8` im Handler |
| `PUT /api/auth/pin` | 8 | `if len(new_pin) < 8` im Handler |
| `POST /api/auth/reset-password` | 8 | Pydantic `Field(min_length=8)` |
| Management-Reset (profiles.py) | 8 | Pydantic `Field(min_length=8)` |
| Frontend LoginPage | 8 | `minLength="8"` |
| Frontend AccountSettingsPage | 8 | `minLength={8}` |
---
### P-07 ALLOW_PUBLIC_MEDIA_STATIC Release-Test ✅
**Status:** Umgesetzt
**Betroffene Dateien:**
- `backend/tests/test_security_release.py` 2 neue Tests
**Tests:** Beide grün.
---
### P-12 sessionStorage bei Logout bereinigen ✅
**Status:** Umgesetzt (2026-05-10, Version 0.8.68)
**Betroffene Dateien:**
- `frontend/src/context/AuthContext.jsx` `logout()`
- `tests/dev-smoke-test.spec.js` neuer Playwright-Test
**Befund (war offen):**
`logout()` löschte nur `localStorage`-Einträge. `TrainingCoachPage` schrieb sessionStorage-Schlüssel mit Präfix `sj_coach_` (`sj_coach_step_{unitId}`, `sj_coach_deltas_{unitId}`, `sj_coach_debrief_{unitId}`), die nach Logout im Tab erhalten blieben. Bei Nutzerwechsel im selben Tab (geteilter Rechner) konnte der neue Nutzer Trainingsfortschritt des Vorgängers sehen.
**Technische Änderung:**
Gezielte Präfix-Löschung aller `sj_coach_*`-Schlüssel beim Logout (kein `sessionStorage.clear()`). Fremde sessionStorage-Schlüssel (Browser-Extensions o. ä.) bleiben erhalten.
```javascript
// AuthContext.jsx logout() — Ergänzung:
for (const key of Object.keys(sessionStorage)) {
if (key.startsWith('sj_coach_')) {
sessionStorage.removeItem(key)
}
}
```
**Begründung gezielte statt vollständiger Löschung:**
Alle Shinkan-spezifischen sessionStorage-Schlüssel sind eindeutig über den Präfix `sj_coach_` identifizierbar (definiert in `TrainingCoachPage.jsx` Zeilen 1525). Ein `sessionStorage.clear()` würde auch Schlüssel fremder Quellen im selben Tab löschen; die Präfix-Löschung ist spezifischer und sicherer.
**Tests:** Playwright E2E-Test `tests/dev-smoke-test.spec.js` (Test „P-12: sessionStorage wird bei Logout bereinigt"):
- Setzt drei `sj_coach_*`-Schlüssel und einen fremden Schlüssel
- Klickt „Abmelden"
- Prüft: `sj_coach_*` → null (entfernt), fremder Schlüssel → erhalten, `authToken` → null (localStorage weiterhin korrekt bereinigt)
**Hinweis Tests:** Das Projekt verfügt über kein Frontend-Unit-Test-Framework (kein Vitest/Jest in package.json). Der Test ist als Playwright E2E-Test implementiert, der einen laufenden Dev-Server voraussetzt. Automatisierte Ausführung der Playwright-Tests erfordert `npx playwright test` mit gesetzter `PLAYWRIGHT_BASE_URL`.
---
### P-23 LoginPage: minLength + Versionsstring ✅
**Status:** Umgesetzt
**Betroffene Dateien:**
- `frontend/src/pages/LoginPage.jsx`
**Technische Änderung:**
- `minLength="6"``minLength="8"`
- Hardcodierter Versionsstring `v0.1.0 • Development` entfernt
---
### P-24 CORS allow_methods und allow_headers einschränken ✅
**Status:** Umgesetzt
**Betroffene Dateien:**
- `backend/main.py:85-86`
**Technische Änderung:**
```python
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "X-Auth-Token", "X-Active-Club-Id"],
```
---
## Test-Zusammenfassung (Stand 0.8.67)
```
tests/test_auth_password_reset_minlength.py 7 passed (neu, P-05b)
tests/test_media_assets_copyright_promotion.py 7 passed (gehärtet, P-04)
tests/test_security_release.py 9 passed (inkl. 2 P-07-Tests)
Weitere bestehende Tests: 81 passed, 6 skipped
Gesamt: 104 passed, 6 skipped, 1 failed
Fehlgeschlagener Test: test_list_media_assets_invalid_lifecycle_400
→ Pre-existing: benötigt laufenden PostgreSQL-Container (Hostname "postgres")
→ Bestand bereits vor allen Compliance-Änderungen (verifiziert per git stash)
```
---
## Nicht umgesetzte Pakete
> Paket-IDs und -Titel gemäß kanonischem Register `docs/compliance-package-register.md`.
> Abweichende Beschreibungen in der Ursprungsversion dieses Abschnitts wurden am 2026-05-10 korrigiert (P-06, P-08, P-09, P-10, P-11, P-18 — Details im Konsistenzbericht des Registers).
| Paket | Kanonischer Titel | Status | Begründung |
|-------|------------------|--------|------------|
| P-01 | Rechtstexte | offen | Scope ausgeschlossen (juristischer Inhalt) |
| P-02 | Self-Service-Kontolöschung + Datenexport | offen | Scope ausgeschlossen |
| P-06 | Upload-Einwilligungsdialog (Recht am eigenen Bild) | offen | Scope ausgeschlossen |
| P-08 | HSTS / externe Proxy-Sicherheit dokumentieren | offen | Scope ausgeschlossen (außerhalb Repo — Reverse-Proxy) |
| P-09 | Admin-Audit-Log | offen | Scope ausgeschlossen |
| P-10 | Mindestalter-Abfrage | offen | Scope ausgeschlossen |
| P-11 | Legal-Hold Lifecycle-Status | offen | Scope ausgeschlossen |
| P-12 | sessionStorage bei Logout bereinigen | ✅ umgesetzt | Version 0.8.68 — siehe §P-12 oben |
| P-13 | Content-Melde-Backend | offen | Scope ausgeschlossen (erst juristisch klären) |
| P-14 | Moderations-UI | offen | Scope ausgeschlossen |
| P-15 | Uploader-Benachrichtigung bei Sperrung | offen | Scope ausgeschlossen |
| P-16 | Beschwerdeverfahren | offen | Scope ausgeschlossen |
| P-17 | MFA für Superadmins (TOTP) | offen | Scope ausgeschlossen |
| P-18 | HttpOnly-Cookie als Auth-Alternative | offen | Scope ausgeschlossen |
| P-19 | Anti-Virus-Scan (ClamAV) | offen | Scope ausgeschlossen |
| P-20 | VVT erstellen | offen | Scope ausgeschlossen (Betreiber-Aufgabe) |
| P-21 | AV-Verträge abschließen | offen | Scope ausgeschlossen (Betreiber-Aufgabe) |
| P-22 | HTML-Sanitizer für Rich-Text-Felder | offen | Scope ausgeschlossen |
---
## Re-Audit-Empfehlung
Operativ nach Deployment 0.8.67 prüfen:
1. **P-03/P-03b**: `docker logs shinkan-retention-cron` — Job läuft täglich 03:00 Uhr; Retention-Zeiten: 30 → 30 Tage
2. **P-04**: Manuell: PATCH privates Medium auf `official` ohne `copyright_notice` → muss 400 liefern
3. **P-05b**: Manuell: Reset-Link mit 7-Zeichen-Passwort → muss mit Fehler abgewiesen werden
4. **P-24**: Browser DevTools Preflight → `Access-Control-Allow-Headers: content-type, x-auth-token, x-active-club-id`
Nächster vollständiger Re-Audit nach Umsetzung der kritischen Findings (P-01: Impressum/Datenschutz, P-02: DSGVO-Löschanfragen).