shinkan-jinkendo/docs/compliance-implementation.md
Lars 1640fe6045
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 54s
feat(compliance): update compliance documents for app version 0.8.83, including full implementation of P-06 and P-06+ features
2026-05-11 12:11:30 +02:00

27 KiB
Raw Blame History

Compliance-Implementierung Umsetzungsbericht

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


Freigegebene Pakete und Umsetzungsstatus

P-01 Rechtstexte ⚠️ (technischer Teil umgesetzt; juristische Inhalte offen)

Status: Technisch teilweise umgesetzt (2026-05-10, Version 0.8.69)

Betroffene Dateien:

  • frontend/src/pages/LegalPage.jsx (neu) — Platzhalter-Komponente für alle vier Seiten
  • frontend/src/App.jsx — 4 neue öffentliche Routen
  • frontend/src/pages/LoginPage.jsx — Rechtstext-Links im Card-Footer
  • frontend/src/components/DesktopSidebar.jsx — Rechtstext-Links im Sidebar-Footer

Technische Änderung:
Vier öffentliche Routen angelegt, kein Auth erforderlich, kein Redirect für nicht eingeloggte Nutzer:

  • /impressum<LegalPage type="impressum" />
  • /datenschutz<LegalPage type="datenschutz" />
  • /nutzungsbedingungen<LegalPage type="nutzungsbedingungen" />
  • /medienrichtlinie<LegalPage type="medienrichtlinie" />

Jede Seite enthält:

  • Deutlich sichtbaren Platzhalterhinweis: „MUSTER / PLATZHALTER Inhalt wird vor Produktivbetrieb juristisch geprüft und durch den Betreiber ergänzt."
  • Strukturierte Pflichtfelder je Rechtstext (Betreiber, Rechtsgrundlagen, Betroffenenrechte etc.)
  • Querlinks zu den anderen drei Rechtstextseiten

Links zu allen vier Seiten sind in der Login-/Registrierungsseite (Card-Footer) und im Desktop-Sidebar-Footer sichtbar ohne Anmeldung erreichbar.

Nicht umgesetzt (außerhalb Freigabe):
Juristische Texte, inhaltliche Überprüfung, Admin-konfigurierbare Inhalte, Einwilligungs-Checkboxen.

KRIT-01 Blocking-Status:
Der Blocker KRIT-01 bleibt offen bis juristisch geprüfte Texte durch den Betreiber eingepflegt sind. Die technische Voraussetzung (Routen und Seitenstruktur) ist erfüllt.

Tests: 5 Playwright-Tests, alle grün:

  • Impressum ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Datenschutz ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Nutzungsbedingungen ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Medienrichtlinie ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Login-Seite enthält alle vier Rechtstext-Links

P-01b Mobile/PWA-Erreichbarkeit der Rechtstexte

Status: Umgesetzt (2026-05-10, Version 0.8.70) — Nacharbeit zu P-01

Betroffene Dateien:

  • frontend/src/pages/SettingsLegalPage.jsx (neu) — Hub-Seite /settings/legal
  • frontend/src/App.jsx — Route /settings/legal im ProtectedLayout
  • frontend/src/pages/AccountSettingsPage.jsx — Link zu /settings/legal

Technische Änderung:
Neue Seite /settings/legal im eingeloggten Einstellungsbereich: Hub-Seite mit Links zu allen vier Rechtstextseiten, erreichbar über Einstellungen → Rechtliches. Entspricht dem bestehenden Pattern von /settings/system. Auf der AccountSettingsPage ist ein Link „Rechtliches" unterhalb der System-Info-Verlinkung ergänzt.

Tests: 3 Playwright-Tests, alle grün (17/17 Gesamt):

  • Einstellungen enthält Link zu /settings/legal
  • /settings/legal enthält Überschrift + alle vier Rechtstext-Links
  • Jeder Link aus /settings/legal führt zur korrekten öffentlichen Route

P-01c Admin-konfigurierbare Rechtstexte

Status: Umgesetzt (2026-05-10, Version 0.8.71) — Nacharbeit zu P-01

Betroffene Dateien:

  • backend/migrations/047_legal_documents.sql (neu) — Tabellen legal_documents + legal_document_audit
  • backend/routers/legal_documents.py (neu) — Öffentliche + Superadmin-Endpoints
  • backend/main.py — Router-Registrierung
  • frontend/src/pages/LegalPage.jsx — API-Fetch mit Fallback auf statischen Platzhalter
  • frontend/src/pages/AdminLegalDocumentsPage.jsx (neu) — Superadmin-UI
  • frontend/src/App.jsx — Route /admin/legal-documents
  • frontend/src/components/AdminPageNav.jsx — Link „Rechtstexte" im Admin-Nav
  • frontend/src/utils/api.js — 8 neue API-Funktionen

Technische Änderung:

Datenbank (Migration 047):
Tabelle legal_documents: versionierte Rechtstexte mit Workflow draft → published → archived. Felder: document_type (impressum | privacy_policy | terms_of_use | media_policy), version (INT, auto-inkrementiert), title, content_sections (JSONB: [{heading, content}]), status, change_note, Timestamps, FK auf Ersteller + Publisher. Partial-Unique-Index: nur ein published-Datensatz pro document_type gleichzeitig. Tabelle legal_document_audit: unveränderlicher Änderungslog je Dokument.

Backend-Endpoints:

Endpoint Auth Beschreibung
GET /api/legal-documents/{type}/published Kein Liefert veröffentlichtes Dokument oder null
GET /api/admin/legal-documents Superadmin Alle Versionen aller Typen
POST /api/admin/legal-documents Superadmin Neuen Entwurf anlegen
GET /api/admin/legal-documents/{id} Superadmin Einzeldokument mit content_sections
PUT /api/admin/legal-documents/{id} Superadmin Entwurf bearbeiten (nur status=draft)
POST /api/admin/legal-documents/{id}/publish Superadmin Veröffentlichen; bisherige Version → archived
POST /api/admin/legal-documents/{id}/archive Superadmin Archivieren
GET /api/admin/legal-documents/{id}/audit Superadmin Änderungslog

Frontend:
LegalPage.jsx ruft beim Laden GET /api/legal-documents/{type}/published ab. Gibt die API null zurück (kein veröffentlichtes Dokument vorhanden), zeigt die Seite weiterhin den bisherigen Platzhalter mit MUSTER-Banner. Ist ein Dokument veröffentlicht, wird dessen Inhalt ohne Platzhalter-Banner angezeigt. AdminLegalDocumentsPage.jsx unter /admin/legal-documents (nur Superadmin) ermöglicht Erstellen, Bearbeiten, Veröffentlichen und Archivieren von Entwürfen mit Tabs pro Dokumententyp und Änderungslog.

Kein neues npm-Paket notwendig — JSONB-Struktur mit {heading, content} statt Markdown; keine XSS-Gefahr.

Tests: 3 Playwright-Tests:

  • Rechtstextseiten laden ohne Fehler (API-fetch mit Fallback)
  • /admin/legal-documents erreichbar für Superadmin mit korrekter Überschrift
  • Admin-Nav enthält Link zu Rechtstexten

P-01c Erweiterung — Als-Entwurf-kopieren

Status: Umgesetzt (2026-05-10, Version 0.8.72) — Ergänzung zu P-01c

Betroffene Dateien:

  • backend/routers/legal_documents.py — Neuer Endpoint POST /api/admin/legal-documents/{id}/copy-as-draft
  • frontend/src/utils/api.jscopyLegalDocumentAsDraft(id)
  • frontend/src/pages/AdminLegalDocumentsPage.jsx — „Als Entwurf kopieren"-Button in der Dokumentliste

Technische Änderung:
Neuer Superadmin-Endpoint kopiert title und content_sections eines bestehenden Dokuments in einen neuen Entwurf. Die Versionsnummer wird dabei automatisch inkrementiert (letztes Dokument dieses Typs + 1). Status ist immer draft. Ermöglicht inkrementelle Überarbeitung ohne vollständige Neueingabe.

Motivation: Bei jeder fälligen Textanpassung mussten alle Abschnitte neu erfasst werden. Die Kopierfunktion ermöglicht, den letzten Stand zu übernehmen und nur die geänderten Abschnitte zu bearbeiten.


P-01c Erweiterung — Echter PDF-Download + Abschnitts-Sortierung

Status: Umgesetzt (2026-05-10, Version 0.8.74) — Ergänzung zu P-01c

Betroffene Dateien:

  • frontend/src/pages/AdminLegalDocumentsPage.jsxgenerateLegalPdf() via jsPDF, SectionEditor mit Sortierung
  • frontend/src/pages/LegalPage.jsxgenerateLegalPdf() via jsPDF, Button „PDF herunterladen"
  • frontend/package.json — neues npm-Paket jspdf

Technische Änderung — PDF:
Ersetzt die bisherige window.open() + window.print()-Lösung (Browser-Druckdialog) durch jsPDF. Die Funktion generateLegalPdf(doc) erzeugt ein A4-PDF client-seitig mit:

  • Titel (bold, 20 pt), Metazeile (Version + Gültigkeitsdatum), Trennlinie
  • Abschnitte mit Heading (bold, 11 pt) und Fließtext (10 pt, splitTextToSize für automatischen Zeilenumbruch)
  • Automatischer Seitenumbruch bei y > 277 mm
  • Footer auf jeder Seite: „Shinkan Jinkendo | Exportiert am DD.MM.YYYY Seite X von Y"
  • Direkter Dateidownload via pdf.save('{document_type}_v{version}.pdf')

Gilt sowohl für die Admin-Seite (Download aus der Dokumentliste, ruft getLegalDocument(id) für Volldokument ab) als auch für LegalPage.jsx (öffentlich, nur bei veröffentlichten Dokumenten).

Technische Änderung — Abschnitts-Sortierung und Einfügen:
SectionEditor in AdminLegalDocumentsPage.jsx erhält:

  • ▲/▼-Buttons pro Abschnitt (deaktiviert an den Rändern) — Reihenfolge per Array-Swap
  • InsertButton zwischen jedem Abschnitt (inkl. vor dem ersten) — fügt leeren Abschnitt an beliebiger Stelle per splice ein
  • Kein Drag-and-Drop-Framework — reine React-State-Manipulation

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

// 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:

allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "X-Auth-Token", "X-Active-Club-Id"],

Test-Zusammenfassung (Stand 0.8.83)

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_media_rights_declaration.py          25 passed  (neu, P-06aP-06d)
tests/test_security_release.py                   9 passed  (inkl. 2 P-07-Tests)
Weitere bestehende Tests:                       81 passed, 6 skipped
Gesamt (Backend):                              129 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)

Playwright E2E (dev-smoke-test.spec.js):        22+ passed (inkl. P-06-Tests)
→ P-01: 4× Route ohne Auth + Platzhalterhinweis + Reload, 1× Login-Links
→ P-01b: 3× /settings/legal (Link in Einstellungen, Überschrift, Rechtstext-Links)
→ P-01c: 3× Admin-Rechtstexte (Seitenladung, Route /admin/legal-documents, Admin-Nav)
→ P-12: sessionStorage-Bereinigung (grün)
→ P-06: 5× RightsDeclarationDialog (Anzeige, Pflichtfeld-Validierung, Upload-Flow,
         Promotions-Dialog, Legacy-Altbestand-Indikator)

Anmerkung jsPDF (0.8.74):
→ PDF-Download via pdf.save() ist ein Browser-Download, kein Server-Request.
→ Kein Backend-Test möglich; funktional im Browser verifizierbar.
→ Playwright-E2E-Test erfordert laufenden Dev-Server (npx playwright test).

Anmerkung P-06+ Journal/Korrektur (0.8.820.8.83):
→ Journal-Endpoint und Korrektur-Endpoint durch manuellen API-Test (curl) auf Dev-System verifiziert.
→ Bugfix club_admin-Prüfung (has_club_role) verifiziert: 500 → 200 nach Fix.
→ Keine dedizierte Playwright-Testsuite für Journal-Modal und Korrektur-Formular (UI-Verifikation ausstehend).

P-06 Upload-Einwilligungsdialog ⚠️ (technisch vollständig umgesetzt inkl. P-06+; juristische Validierung offen)

Status: Technisch vollständig umgesetzt (inkl. P-06+ Volljournal + Korrektur, Version 0.8.83) — KRIT-04 bleibt offen.

Deklarationsversion: p06-v1-conservative


P-06aP-06d — Kernum­setzung (Version 0.8.75)

Betroffene Dateien:

  • backend/migrations/048_media_rights_declarations.sql (neu) — Append-only Deklarations-Log + 3 Schnellfelder in media_assets (rights_status, rights_declared_for_visibility, rights_declared_at)
  • backend/migrations/049_media_rights_consent_context.sql (neu) — Kontext-Freitextfelder in media_asset_rights_declarations (person_consent_context, parental_consent_context, music_rights_context, third_party_rights_context)
  • backend/media_rights.py (neu) — Zentrales Policy-Modul: validate_rights_declaration, check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link, write_rights_declaration, update_rights_quick_fields
  • backend/routers/media_assets.py — P-06-Enforcement in Bulk-Upload, PATCH, Bulk-PATCH; 3 neue Endpoints
  • backend/routers/exercises.py — P-06 bei upload_exercise_media (neue Assets) und attach_exercise_media_from_asset
  • frontend/src/components/RightsDeclarationDialog.jsx (neu) — Einwilligungsdialog (9 Pflichtfelder + Kontext-Freitexte)
  • frontend/src/pages/MediaLibraryPage.jsx — Dialog-Integration vor Bulk-Upload; Altbestand-Indikator
  • frontend/src/pages/ExerciseInlineFileMediaModal.jsx + ExerciseInlineEmbedModal.jsx — RightsDeclarationDialog vor Upload
  • frontend/src/utils/api.jsbulkUploadMediaAssets erweitert um P-06-Felder
  • backend/tests/test_media_rights_declaration.py (neu) — 25 Unit/HTTP-Tests

Neue Endpoints (P-06b):

Endpoint Beschreibung
POST /api/media-assets/{id}/rights-declarations Explizite Re-/Nachdeklaration
GET /api/admin/media-rights/legacy-summary Zusammenfassung Altbestand nach Sichtbarkeit (Plattform-Admin)
GET /api/admin/media-rights/legacy-assets Paginierte Liste Altbestand club/official (Plattform-Admin)

Abweichung von Spec §3 (konservative Erstannahme):
Person-Fragen sind auch bei Sichtbarkeit private Pflicht (§10.1 in docs/p06-upload-rights-spec.md).

Altbestand (Legacy):
Alle vor Migration 048 hochgeladenen Medien erhalten rights_status = 'legacy_unreviewed'.
Promotion blockiert bis Nachdeklaration. In Bibliotheks-UI als „Altbestand ⚠" markiert.


P-06+ — Volljournal + Korrektur (Version 0.8.820.8.83)

Motivation: Vollständige Nachvollziehbarkeit aller Medien-Ereignisse, nicht nur der Deklarationen. Plus: Möglichkeit, fehlerhafte Deklarationen mit Begründung nachträglich zu korrigieren (append-only — neueste gilt).

Betroffene Dateien:

  • backend/migrations/050_media_audit_log.sql (neu) — Neue Tabelle media_asset_audit_log + correction_note TEXT in Deklarations-Tabelle + erweiterter CHECK (action_type += 'correction')
  • backend/media_rights.py — Neue Funktionen: write_audit_log_entry(), write_rights_correction_declaration()
  • backend/routers/media_assets.py — Neues Pydantic-Model RightsCorrectionBody; PATCH-Endpoint schreibt Audit-Log-Einträge; Lifecycle-Aktionen schreiben lifecycle_change-Einträge; 2 neue Endpoints; Import has_club_role ergänzt
  • frontend/src/pages/MediaLibraryPage.jsx — Journal-Modal komplett überarbeitet: unified events[]-Ansicht; Korrektur-Formular inline; Helper-Funktionen actionTypeLabel, eventTypeLabel, visLabel
  • frontend/src/utils/api.js — Neue Funktion addMediaAssetDeclarationCorrection(assetId, body)
  • frontend/src/app.css — CSS für Audit-Einträge (--audit), Korrektur-Einträge (--correction), Korrektur-Formular

Neue Endpoints (P-06+):

Endpoint Auth Beschreibung
GET /api/admin/media-rights/assets/{id}/journal Superadmin / Uploader / Vereins-Admin Volljournal: events[] aus Deklarationen + Audit chronologisch gemischt. Gibt can_correct zurück.
POST /api/admin/media-rights/assets/{id}/correction Superadmin / Uploader / Vereins-Admin Korrektur-Deklaration (append-only, neueste gilt). Felder = P-06-Dialog + correction_note.

Automatische Audit-Log-Einträge:

event_type Auslöser
visibility_change PATCH wenn visibility oder club_id sich ändert
copyright_change PATCH wenn copyright_notice sich ändert
metadata_change PATCH wenn original_filename etc. sich ändert
lifecycle_change Lifecycle-Aktionen: trash_soft, trash_hidden, recover, reactivate (nicht bei Hard-Delete/Purge)

Bugfixes in P-06+:

Bug Fix
Journal + Korrektur gaben 500 (falsches Schema club_admin) has_club_role(cur, profile_id, club_id, "club_admin") statt AND role = 'admin' in club_members
Frontend-Build-Fehler: doppelte lcLabel-Deklaration Duplikat in Zeile 264 von MediaLibraryPage.jsx entfernt

KRIT-04 Status:
Offen. Juristische Validierung der Feldtexte (§10.3 p06-v1-conservative, T1T10), KUG/DSGVO-Anforderungen (§7.1§7.12 der Spec) und Korrekturfähigkeit (Spec §11.9) steht aus.
Referenz: docs/p06-upload-rights-spec.md §10.5, §11.9.


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) teilweise umgesetzt Technisch umgesetzt (2026-05-11, v0.8.75) unter vorläufigen Erstannahmen p06-v1-conservative — siehe §P-06 unten. KRIT-04 bleibt offen bis juristische Validierung.
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 prüfen (Stand 0.8.83):

  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
  5. P-06: Manuell: Upload ohne rights_holder_confirmed → muss 400 liefern; Journal-Endpoint für vorhandene Assets → muss 200 + events[] liefern; Korrektur-Endpoint → muss neue Deklaration mit action_type='correction' schreiben
  6. P-06 Audit-Log: PATCH Sichtbarkeit eines Assets → media_asset_audit_log muss Eintrag visibility_change enthalten

Nächster vollständiger Re-Audit empfohlen nach juristischer Klärung P-06/KRIT-04 (Textfreigabe T1T10) und nach Einpflegen der Rechtstexte P-01 durch Betreiber.