shinkan-jinkendo/docs/compliance-implementation.md
Lars fc33bfbdeb
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 26s
feat(compliance): update retention policy and enhance password reset validation
- Adjusted retention policy to align with compliance requirements:
  - Changed HIDDEN_TO_PURGE_DAYS from 90 to 30 days.
- Enhanced password reset functionality to enforce a minimum password length of 8 characters.
- Updated tests to validate new password requirements and retention logic.
- Corrected umlaut in copyright error messages for clarity.
2026-05-10 08:26:15 +02:00

9.3 KiB
Raw Blame History

Compliance-Implementierung Umsetzungsbericht

Erstellt: 2026-05-09
Zuletzt aktualisiert: 2026-05-10
Audit-Basis: docs/compliance-audit.md
App-Version nach Umsetzung: 0.8.67


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:

# 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.


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:

# 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: Nicht umgesetzt — weiterhin offen (MITT-05)

Befund:
logout() in frontend/src/context/AuthContext.jsx löscht nur localStorage-Einträge:

const logout = () => {
    setUser(null)
    localStorage.removeItem('authToken')
    localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)
    // sessionStorage wird NICHT geleert
}

TrainingCoachPage schreibt folgende Schlüssel in sessionStorage:

  • storageStepKey(unitId) aktueller Trainingsschritt (Zahl)
  • storageDeltasKey(unitId) Trainingsdeltas (JSON)
  • storageDebriefKey(unitId) Debrief-Status (Boolean)

Nach einem Logout bleiben diese Daten im sessionStorage des Tabs erhalten. Bei einem Nutzerwechsel im selben Tab (geteilter Rechner) kann der neue Nutzer Trainingsfortschritt des Vorgängers sehen, bis der Tab geschlossen wird.

Risikoeinstufung: MITT-05 (mittleres Risiko; sessionStorage ist tab-lokal und wird beim Tab-Schließen gelöscht; erfordert physischen Zugang zum selben offenen Tab)

Fix (nicht durchgeführt, ca. 15 Minuten Aufwand):

// AuthContext.jsx logout():
const logout = () => {
    setUser(null)
    localStorage.removeItem('authToken')
    localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)
    sessionStorage.clear()  // oder gezielt nach 'shinkan_coach_' prefix
}

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:

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 Status Begründung
P-01 offen Rechtstexte — Scope ausgeschlossen
P-02 offen Self-Service-Löschworkflow — Scope ausgeschlossen
P-06 offen HSTS-Header — Scope ausgeschlossen
P-09 offen Kein Einwilligungsdialog Recht am eigenen Bild — Scope ausgeschlossen
P-10 offen DSA-Meldeverfahren — Scope ausgeschlossen
P-11 offen HttpOnly-Cookie-Migration — Scope ausgeschlossen
P-12 offen sessionStorage bei Logout — nicht freigegeben, Aufwand ca. 15 min
P-13P-16 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).