Some checks failed
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Failing after 1m23s
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).
Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
media_asset_rights_declarations (Append-only Audit-Log), Felder
rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert
Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
(konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet
Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt
Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log
Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.
version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
450 lines
23 KiB
Markdown
450 lines
23 KiB
Markdown
# Compliance-Implementierung – Umsetzungsbericht
|
||
|
||
**Erstellt:** 2026-05-09
|
||
**Zuletzt aktualisiert:** 2026-05-11
|
||
**Audit-Basis:** `docs/compliance-audit.md`
|
||
**App-Version nach Umsetzung:** 0.8.75
|
||
|
||
---
|
||
|
||
## 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.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, `SectionEditor` mit Sortierung
|
||
- `frontend/src/pages/LegalPage.jsx` — `generateLegalPdf()` 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:**
|
||
```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 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 • 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.74)
|
||
|
||
```
|
||
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 (Backend): 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)
|
||
|
||
Playwright E2E (dev-smoke-test.spec.js): 17 passed (inkl. P-01c-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)
|
||
|
||
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).
|
||
```
|
||
|
||
---
|
||
|
||
### P-06 – Upload-Einwilligungsdialog ⚠️ (technisch umgesetzt; juristische Validierung offen)
|
||
|
||
**Status:** Technisch umgesetzt (2026-05-11, Version 0.8.75) unter vorläufigen Erstannahmen — KRIT-04 bleibt offen.
|
||
|
||
**Deklarationsversion:** `p06-v1-conservative`
|
||
|
||
**Betroffene Dateien:**
|
||
- `backend/migrations/048_media_rights_declarations.sql` (neu) — Append-only Deklarations-Log + 3 Schnellfelder in `media_assets`
|
||
- `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 Felder)
|
||
- `frontend/src/pages/MediaLibraryPage.jsx` — Dialog-Integration vor Bulk-Upload; Altbestand-Indikator
|
||
- `frontend/src/utils/api.js` — `bulkUploadMediaAssets` erweitert um P-06-Felder
|
||
- `backend/tests/test_media_rights_declaration.py` (neu) — 17 Unit/HTTP-Tests
|
||
- `tests/dev-smoke-test.spec.js` — 5 P-06 E2E-Tests
|
||
|
||
**Neue Endpoints:**
|
||
- `POST /api/media-assets/{id}/rights-declarations` — Explizite Re-/Nachdeklaration
|
||
- `GET /api/admin/media-rights/legacy-summary` — Zusammenfassung Altbestand (Plattform-Admin)
|
||
- `GET /api/admin/media-rights/legacy-assets` — Paginierte Liste Altbestand (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.
|
||
|
||
**KRIT-04 Status:**
|
||
Offen. Juristische Validierung der Feldtexte, Einwilligungsformulierungen und Altbestand-Behandlung steht aus.
|
||
Referenz: `docs/p06-upload-rights-spec.md` §10.5.
|
||
|
||
---
|
||
|
||
## 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 nach Deployment 0.8.69 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).
|