- Added new API endpoints for content reporting, including submission, retrieval, and status updates. - Created database migration for `content_reports` table to store report data. - Integrated content reports into the existing admin inbox for better management. - Implemented validation for report submissions, including required fields and email format. - Added tests for content reporting functionality, covering various scenarios and edge cases. - Updated frontend API utility to include new content report methods. - Bumped app version to 0.8.87 and updated relevant page versions.
35 KiB
Compliance-Implementierung – Umsetzungsbericht
Erstellt: 2026-05-09
Zuletzt aktualisiert: 2026-05-11
Audit-Basis: docs/compliance-audit.md
App-Version nach Umsetzung: 0.8.87
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 Seitenfrontend/src/App.jsx— 4 neue öffentliche Routenfrontend/src/pages/LoginPage.jsx— Rechtstext-Links im Card-Footerfrontend/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/legalfrontend/src/App.jsx— Route/settings/legalim ProtectedLayoutfrontend/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/legalenthält Überschrift + alle vier Rechtstext-Links- Jeder Link aus
/settings/legalfü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) — Tabellenlegal_documents+legal_document_auditbackend/routers/legal_documents.py(neu) — Öffentliche + Superadmin-Endpointsbackend/main.py— Router-Registrierungfrontend/src/pages/LegalPage.jsx— API-Fetch mit Fallback auf statischen Platzhalterfrontend/src/pages/AdminLegalDocumentsPage.jsx(neu) — Superadmin-UIfrontend/src/App.jsx— Route/admin/legal-documentsfrontend/src/components/AdminPageNav.jsx— Link „Rechtstexte" im Admin-Navfrontend/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-documentserreichbar 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 EndpointPOST /api/admin/legal-documents/{id}/copy-as-draftfrontend/src/utils/api.js—copyLegalDocumentAsDraft(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.jsx—generateLegalPdf()via jsPDF,SectionEditormit Sortierungfrontend/src/pages/LegalPage.jsx—generateLegalPdf()via jsPDF, Button „PDF herunterladen"frontend/package.json— neues npm-Paketjspdf
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,
splitTextToSizefü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
InsertButtonzwischen jedem Abschnitt (inkl. vor dem ersten) — fügt leeren Abschnitt an beliebiger Stelle perspliceein- Kein Drag-and-Drop-Framework — reine React-State-Manipulation
P-03 – Papierkorb-Retention-Job aktivieren ✅
Status: Umgesetzt
Betroffene Dateien:
docker-compose.yml– neuer Serviceretention-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– DefaultHIDDEN_TO_PURGE_DAYSdocker-compose.yml– explizite Env-Variable imretention-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.
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()undbulk_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
clubohne Copyright → 400 (exakt) - Promotion zu
officialohne Copyright → 400 (exakt) - Promotion zu
clubmit 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→< 8frontend/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_passwordbackend/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-11 – Legal-Hold Lifecycle-Status ✅
Status: Vollständig umgesetzt (2026-05-11, Version 0.8.84 + Nachfixe 0.8.85–0.8.86)
P-11 Kernumsetzung (Version 0.8.84)
Betroffene Dateien:
backend/migrations/051_legal_hold.sql– neue Spaltenlegal_hold_active,legal_hold_reason_code,legal_hold_reason_note,legal_hold_set_by_profile_id,legal_hold_set_at,legal_hold_released_by_profile_id,legal_hold_released_at,legal_hold_release_noteinmedia_assets; Audit-Log-CHECK umlegal_hold_set/legal_hold_releasederweitertbackend/media_legal_hold.py– Neu: zentrale Servicesset_legal_hold,release_legal_hold,assert_not_under_legal_hold,is_media_available_for_normal_use,assert_superadmin_for_legal_holdbackend/media_lifecycle.py–run_retention_pass()filtertAND (legal_hold_active = FALSE OR legal_hold_active IS NULL)in beiden Retention-Queriesbackend/routers/media_assets.py–admin_legal_hold_routermit drei Endpoints;_list_active_visibility_clause()berücksichtigtinclude_legal_hold-Parameter;download_media_asset_file()prüft Legal-Hold für Nicht-Superadminsbackend/routers/exercises.py–attach_exercise_media_from_asset()ruftassert_not_under_legal_hold()vor Verknüpfung aufbackend/main.py–app.include_router(media_assets.admin_legal_hold_router)frontend/src/utils/api.js–setMediaAssetLegalHold,releaseMediaAssetLegalHold,listMediaAssetsWithLegalHoldfrontend/src/pages/MediaLibraryPage.jsx– Legal-Hold-Badge auf Kacheln; Superadmin-Aktionen „Sofort sperren"/„Sperre aufheben" im Edit-Modal; Bestätigungs-Dialog mit Pflichtfeldern; Journal-Renderpfad fürlegal_hold_set/legal_hold_releasedfrontend/src/app.css– CSS-Klassen für Legal-Hold-Badge, -Dialog, -Button
Neue API-Endpoints (Superadmin):
POST /api/admin/media-assets/{asset_id}/legal-hold— Sofortsperre setzen (reason_code + reason_note Pflicht)POST /api/admin/media-assets/{asset_id}/legal-hold/release— Sofortsperre aufheben (release_note Pflicht)GET /api/admin/media-assets/legal-hold— Liste aller aktuell gesperrten Assets
Tests: 15 Backend-Unit-Tests in backend/tests/test_p11_legal_hold.py — alle grün.
P-11 Nachfixe – UI-Bugs und Sichtbarkeitskorrektur (Version 0.8.85–0.8.86)
Nach Erstimplementierung wurden beim Testen weitere Mängel festgestellt und behoben:
Version 0.8.85 – UI-Bugfixes:
- Fix:
submitLegalHoldriefloadMedia()stattloadItems()auf → "loadMedia is not a function" beim Sperren - Fix: Listabfrage in
list_media_assetsenthieltlegal_hold_active,reason_code,reason_note,set_atnicht → Badge und „Sperre aufheben"-Button im Modal waren nie sichtbar - Fix: Journal-Renderpfad verwendete Keys
nw.legal_hold_reason_code/notestattnw.reason_code/note(Audit-Log-Format) → Begründung und Kommentar nicht angezeigt - Fix: Archiv-Picker (ExerciseFormPage, ExerciseInlineFileMediaModal) filterte Legal-Hold-Assets bereits client-seitig heraus
Version 0.8.86 – Vollständige Absicherung der Auslieferung:
Betroffene Dateien:
backend/routers/exercises.py–download_exercise_media_file()gibt HTTP 451 zurück wennasset_legal_hold_active=TRUE(Datei wird nicht ausgeliefert);enrich_exercise_detail()SELECT erweitert umma.legal_hold_active AS asset_legal_hold_activebackend/routers/media_assets.py–list_media_assetsübergibtinclude_legal_hold=is_supstattinclude_legal_hold=(is_plat or is_sup)— Legal-Hold-Assets nur noch für Superadmin sichtbar, nicht für alle Plattform-Adminsfrontend/src/components/ExerciseMediaEmbed.jsx– Zeigt „Medium nicht verfügbar (gesperrt)" statt Datei wennasset_legal_hold_activefrontend/src/components/ExerciseMediaThumbTile.jsx– Zeigt rot-markierte „Gesperrt"-Kachel statt Dateivorschau; kein Preview-Triggerfrontend/src/pages/ExerciseFormPage.jsx– Vorschau-Modal zeigt Hinweis statt Datei wennasset_legal_hold_active
Sicherheitsarchitektur (vollständig)
- Legal-Hold ist orthogonal zum normalen Papierkorb-Lifecycle (P-03) — kein 30-Tage-Warten
rights_status='blocked'wird als Schnell-Spiegel gesetzt und bei Aufhebung basierend auf vorhandenen Deklarationen wiederhergestellt (declaredwenn Deklaration vorhanden, sonstlegacy_unreviewed)- Nur Superadmin darf setzen/aufheben; nur Superadmin sieht Legal-Hold-Assets in der Medienliste; normale Nutzer sehen gesperrte Assets nicht
- Retention-Job überspringt Legal-Hold-Assets (verhindert versehentliche Löschung unter laufender Sperrmaßnahme)
assert_not_under_legal_hold()blockiert das Verknüpfen von Legal-Hold-Assets mit Übungen- Dateiauslieferung (
download_exercise_media_file) gibt HTTP 451 zurück — keine Umgehung via direkten File-Endpoint - Frontend-Komponenten zeigen Placeholder statt Datei, auch wenn das Asset bereits in einer Übung verknüpft ist
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 15–25). 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 • Developmententfernt
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.86)
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-06a–P-06d)
tests/test_security_release.py 9 passed (inkl. 2 P-07-Tests)
tests/test_p11_legal_hold.py 15 passed (neu, P-11)
Weitere bestehende Tests: 81 passed, 6 skipped
Gesamt (Backend): 144 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.82–0.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).
Anmerkung P-11 Frontend-Absicherung (0.8.85–0.8.86):
→ UI-Fixes (loadItems, Badge-Sichtbarkeit, Journal-Keys) manuell auf Dev-System verifiziert.
→ 15 Backend-Unit-Tests decken Services und Retention-Schutz ab.
→ Keine Playwright-Tests für Legal-Hold-Aktionen im Modal — manuelle UI-Verifikation.
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-06a–P-06d — Kernumsetzung (Version 0.8.75)
Betroffene Dateien:
backend/migrations/048_media_rights_declarations.sql(neu) — Append-only Deklarations-Log + 3 Schnellfelder inmedia_assets(rights_status,rights_declared_for_visibility,rights_declared_at)backend/migrations/049_media_rights_consent_context.sql(neu) — Kontext-Freitextfelder inmedia_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_fieldsbackend/routers/media_assets.py— P-06-Enforcement in Bulk-Upload, PATCH, Bulk-PATCH; 3 neue Endpointsbackend/routers/exercises.py— P-06 beiupload_exercise_media(neue Assets) undattach_exercise_media_from_assetfrontend/src/components/RightsDeclarationDialog.jsx(neu) — Einwilligungsdialog (9 Pflichtfelder + Kontext-Freitexte)frontend/src/pages/MediaLibraryPage.jsx— Dialog-Integration vor Bulk-Upload; Altbestand-Indikatorfrontend/src/pages/ExerciseInlineFileMediaModal.jsx+ExerciseInlineEmbedModal.jsx— RightsDeclarationDialog vor Uploadfrontend/src/utils/api.js—bulkUploadMediaAssetserweitert um P-06-Felderbackend/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.82–0.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 Tabellemedia_asset_audit_log+correction_note TEXTin 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-ModelRightsCorrectionBody; PATCH-Endpoint schreibt Audit-Log-Einträge; Lifecycle-Aktionen schreibenlifecycle_change-Einträge; 2 neue Endpoints; Importhas_club_roleergänztfrontend/src/pages/MediaLibraryPage.jsx— Journal-Modal komplett überarbeitet: unifiedevents[]-Ansicht; Korrektur-Formular inline; Helper-FunktionenactionTypeLabel,eventTypeLabel,visLabelfrontend/src/utils/api.js— Neue FunktionaddMediaAssetDeclarationCorrection(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, T1–T10), 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.
P-13 – Content-Melde-Backend ✅
Status: Vollständig umgesetzt (2026-05-11, Version 0.8.87)
Finding: KRIT-03
Architekturentscheidung: Anstelle einer separaten Moderations-Queue wurde die bestehende Admin-Inbox (InboxPage.jsx) um einen zweiten Abschnitt erweitert. Keine generische inbox_items-Tabelle, keine separate /api/admin/reports-Queue.
Betroffene Dateien:
backend/migrations/052_content_reports.sql(neu) — Tabellecontent_reportsmit Status-Workflow, Priorisierung, 3 Indizesbackend/routers/content_reports.py(neu) — alle Endpointsbackend/tests/test_p13_content_reports.py(neu) — 15 Unit-Testsbackend/main.py— Router-Registrierungbackend/version.py— Version 0.8.87, content_reports 1.0.0frontend/src/utils/api.js— 5 neue API-Funktionen (submitContentReport, getInboxContentReports, getContentReport, patchContentReport, setLegalHoldFromReport)frontend/src/context/OrgInboxContext.jsx— contentReports-State, contentReportCount, canAccessContentReports, isSuperadminfrontend/src/pages/InboxPage.jsx— zweiter Abschnitt „Inhaltsmeldungen", ReportDetailModal
API-Endpoints:
| Endpoint | Auth | Beschreibung |
|---|---|---|
POST /api/content-reports |
Optional | Meldung einreichen (official-Medien ohne Login; eingeloggt: alle sichtbaren Medien) |
GET /api/me/inbox/content-reports |
Plattform-Admin | Liste aller Meldungen, JOIN auf Zieltabellen |
GET /api/content-reports/{id} |
Plattform-Admin | Einzel-Detail |
PATCH /api/content-reports/{id} |
Plattform-Admin | Status/Notiz/Zuweisung; resolution_note Pflicht für Abschluss-Status |
POST /api/content-reports/{id}/legal-hold |
Superadmin | Legal Hold via P-11 set_legal_hold(); setzt Status auf resolved_legal_hold |
Reason-Code-Mapping (P-13 → P-11):
| Meldegrund | Legal-Hold reason_code |
|---|---|
| copyright | copyright_complaint |
| image_rights | rights_dispute |
| privacy | privacy_complaint |
| minors | youth_protection |
| illegal_content | illegal_content |
| youth_protection | youth_protection |
| offensive_content | illegal_content |
| other | other |
Nicht in P-13-Scope: P-14 (Moderations-UI), P-15 (Uploader-Benachrichtigung per E-Mail), P-16 (Beschwerdeverfahren). SMTP-Infrastruktur vorhanden; E-Mail-Benachrichtigungen folgen in P-15.
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 | ✅ umgesetzt | Version 0.8.84–0.8.86 — siehe §P-11 oben |
| P-12 | sessionStorage bei Logout bereinigen | ✅ umgesetzt | Version 0.8.68 — siehe §P-12 oben |
| P-13 | Content-Melde-Backend | ✅ umgesetzt | Version 0.8.87 — siehe §P-13 unten |
| 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.87):
- P-03/P-03b:
docker logs shinkan-retention-cron— Job läuft täglich 03:00 Uhr; Retention-Zeiten: 30 → 30 Tage - P-04: Manuell: PATCH privates Medium auf
officialohnecopyright_notice→ muss 400 liefern - P-05b: Manuell: Reset-Link mit 7-Zeichen-Passwort → muss mit Fehler abgewiesen werden
- P-24: Browser DevTools Preflight →
Access-Control-Allow-Headers: content-type, x-auth-token, x-active-club-id - 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 mitaction_type='correction'schreiben - P-06 Audit-Log: PATCH Sichtbarkeit eines Assets →
media_asset_audit_logmuss Eintragvisibility_changeenthalten - P-11: Superadmin → Medium sperren → in Übung öffnen → Kachel zeigt „Gesperrt"; direkter Dateiaufruf
/exercises/{id}/media/{mid}/file→ muss HTTP 451 liefern; Plattform-Admin (kein Superadmin) → gesperrtes Medium darf in Medienliste nicht erscheinen - P-13: Anonym →
POST /api/content-reportsfür ein official-Medium ohne Auth → muss 200 liefern; gültige Meldung ohnegood_faith_confirmed=true→ muss 400 liefern; Plattform-Admin →GET /api/me/inbox/content-reports→ Liste; Superadmin →POST /api/content-reports/{id}/legal-hold→ setzt Legal Hold + Report-Statusresolved_legal_hold
Nächster vollständiger Re-Audit empfohlen nach juristischer Klärung P-06/KRIT-04 (Textfreigabe T1–T10) und nach Einpflegen der Rechtstexte P-01 durch Betreiber.