Files
WebAPP/CLAUDE.md
Rhino 2e24a40d68 Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill
- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:30:37 +01:00

122 lines
7.4 KiB
Markdown

# WebApp_Install — Handball Team Manager
## Projekt-Typ
Laravel 12 WebApp zur Verwaltung von Handball-Teams, Spielern, Eltern, Terminen und Dateien.
PHP 8.2+, SQLite/MySQL, Blade + Alpine.js + Tailwind CSS, Quill.js WYSIWYG-Editor.
## Architektur
### Verzeichnisstruktur
- `app/Models/` — Eloquent Models (User, Player, Team, Event, Setting, etc.)
- `app/Http/Controllers/Admin/` — Admin-Bereich (UserController, SettingsController, etc.)
- `app/Http/Controllers/Auth/` — Login, Register, ForgotPassword, ResetPassword
- `app/Http/Middleware/` — InstallerMiddleware (Setup-Token), SetLocaleMiddleware, SecurityHeadersMiddleware, ActiveUserMiddleware, DsgvoConsentMiddleware
- `app/Services/` — HtmlSanitizerService (HTMLPurifier), GeocodingService, SupportApiService
- `app/Enums/` — UserRole, EventType, EventStatus, ParticipantStatus, CateringStatus
- `app/Notifications/` — ResetPasswordNotification (Custom, mit Setting-Template)
- `resources/views/` — Blade-Templates (layouts: admin, guest, app)
- `lang/{de,en,pl,ru,ar,tr}/` — 6 Sprachen: Deutsch, Englisch, Polnisch, Russisch, Arabisch, Turkisch
- `database/migrations/` — Nummeriert mit Prefix 0001-0035
- `database/seeders/` — AdminSeeder (benotigt ADMIN_EMAIL + ADMIN_PASSWORD in .env)
### Wichtige Patterns
- **Settings**: Key-Value Store in `settings` Tabelle. `Setting::get($key)` mit 1-Stunden-Cache, `Setting::set($key, $value)` mit Cache-Invalidierung
- **Locale-Settings**: Rechtliche Texte und E-Mail-Templates sind locale-suffixed: `impressum_html_de`, `datenschutz_html_en`, `password_reset_email_pl` etc.
- **Passwort-Hashing**: User-Model nutzt Laravel Cast `'password' => 'hashed'` — KEIN manuelles Hash::make beim Setzen via `$user->password = $pw` noetig
- **HTML-Output**: Alle `{!! !!}`-Ausgaben von User-Content muessen durch `HtmlSanitizerService::sanitize()` gehen
- **Rollen**: Admin, Coach, ParentRep, User (Enum `UserRole`)
- **Soft-Deletes**: User und Player (7 Tage Wiederherstellung)
- **Activity-Log**: `ActivityLog::log()` / `ActivityLog::logWithChanges()` fuer Audit-Trail
### Multi-Language
- 6 Sprachen: de, en, pl, ru, ar, tr
- Translation-Dateien: `lang/{locale}/admin.php`, `auth_ui.php`, `passwords.php`, `ui.php`, `events.php`, `profile.php`, `validation.php`
- Locale wird per SetLocaleMiddleware aus `$user->locale` oder Session gesetzt
- RTL-Support fuer Arabisch (ar)
### Password Reset Flow
1. User klickt "Passwort vergessen?" auf Login-Seite
2. ForgotPasswordController sendet Reset-Link via Laravel Password Broker
3. ResetPasswordNotification laedt optionalen Custom-Template-Text aus `Setting::get('password_reset_email_{locale}')`
4. Platzhalter: `{name}`, `{link}`, `{app_name}`
5. Fallback auf Standard-Laravel-Template mit Translation-Keys (`passwords.reset_*`)
### Installer
- InstallerMiddleware prueft `storage/installed` Datei
- Setup-Token-Schutz: Token in `storage/setup-token`, wird beim ersten Zugriff generiert und in Laravel-Log geschrieben
- Nach Installation wird `storage/installed` erstellt
### Security
- CSP und Permissions-Policy via SecurityHeadersMiddleware (inkl. COOP-Header)
- Honeypot-Feld auf Login/Register-Formularen
- Rate-Limiting auf Auth-Routes (`throttle:login`)
- DSGVO-Consent-System mit Datei-Upload und Admin-Bestaetigung
- Factory Reset benoetigt Passwort + Bestaetigung "RESET-BESTAETIGT"
- **File-Autorisierung**: `authorizeFileAccess()` in FileController prueft Team-Zugehoerigkeit und aktive Kategorien
- **Settings-Whitelist**: SettingsController akzeptiert nur bekannte Keys + validierte Locale-Suffixe
- **SSRF-Schutz**: GeocodingService mit Host-Whitelist (nominatim.openstreetmap.org, photon.komoot.io), HTTPS-only, Timeout 5s
- **Installer-Sicherheit**: Admin-Passwort wird sofort gehasht (nicht Klartext in Session), Setup-Token nur als SHA256 geloggt
- **Path-Traversal-Schutz**: DSGVO-Datei-Downloads pruefen `str_starts_with('dsgvo/')`
- **HTML-Sanitisierung**: Alle `{!! !!}`-Ausgaben (Slogan, Settings-Editor, PWA-Banner) durch `HtmlSanitizerService::sanitize()` geschuetzt
## Conventions
- Controller-Methoden: resourceful (index, create, store, show, edit, update, destroy)
- Blade-Components: `<x-layouts.admin>`, `<x-layouts.guest>`, `<x-layouts.app>`
- Alpine.js fuer interaktive UI-Elemente
- Quill.js v1.3.7 fuer WYSIWYG-Editoren (via CDN)
- Keine Tests vorhanden — bei Aenderungen manuell testen
- Commit-Sprache: Deutsch oder Englisch
- Alle Uebersetzungsschluessel muessen in ALLEN 6 Sprachen hinzugefuegt werden
## Haeufige Aufgaben
### Neuen Translation-Key hinzufuegen
Immer in allen 6 Dateien: `lang/de/`, `lang/en/`, `lang/pl/`, `lang/ru/`, `lang/ar/`, `lang/tr/`
### Neues Setting hinzufuegen
1. Migration erstellen: `Setting::firstOrCreate(['key' => '...'], [...])`
2. In SettingsController `edit()` laden
3. In View rendern
4. In `update()` speichern (mit Sanitization fuer HTML)
### Neues locale-spezifisches Setting
1. Keys: `{setting_name}_{locale}` (z.B. `impressum_html_de`)
2. Migration fuer alle 6 Locales
3. In SettingsController `$localeSettings` Array aufnehmen
4. In View mit Sprach-Flaggen-Selector anzeigen
5. In `update()` mit `str_starts_with()` und `updateOrCreate()` behandeln
6. **Wichtig**: Neuen Key zur Whitelist in `SettingsController::update()` hinzufuegen ($allowedLocaleKeys)
## Security Audit & Haertung (Maerz 2026)
### Audit-Report
- **Datei**: `security-audit-2026-03.html` — Vollstaendiger HTML-Report mit allen Findings und Fix-Dokumentation
- **Score**: 6.5 → 8.5 / 10 nach Haertung
- **Ergebnis**: 20 Findings identifiziert, 19 behoben, 1 als akzeptiertes Risiko
### Behobene Schwachstellen (nach Datei)
| Datei | Findings | Aenderungen |
|-------|----------|-------------|
| `FileController.php` | F01 (Kritisch) | `authorizeFileAccess()` — IDOR-Schutz fuer Downloads/Previews |
| `admin/settings/edit.blade.php` | F02 (Kritisch) | Alle `{!! !!}` durch `HtmlSanitizerService::sanitize()` |
| `layouts/app.blade.php`, `guest.blade.php` | F03 (Kritisch) | Slogan-Ausgabe sanitisiert |
| `Admin/SettingsController.php` | F04 (Hoch) | Settings-Key-Whitelist in `update()` |
| `Services/GeocodingService.php` | F06 (Hoch) | ALLOWED_HOSTS, HTTPS-only, SHA256-Cache, Timeout |
| `EventController.php` | F07 (Hoch) | `accessibleTeamIds()`, EventType-Validierung, int-Cast |
| `InstallerController.php` | F08 (Mittel) | Passwort sofort gehasht (`installer.admin_password_hash`) |
| `InstallerMiddleware.php` | F09 (Mittel) | Token als SHA256 geloggt, chmod 0600 |
| `SecurityHeadersMiddleware.php` | F10, F19 | COOP-Header hinzugefuegt |
| `.env.example` | F11 (Mittel) | SESSION_SECURE_COOKIE + SESSION_SAME_SITE Defaults |
| `Admin/TeamController.php` | F12 (Mittel) | Ziel-Team aktiv-Pruefung in `updatePlayerTeam()` |
| `Admin/ActivityLogController.php` | F13, F16 | whereRaw→Eloquent, `canViewActivityLog()` statt `id !== 1` |
| `CommentController.php` | F14 (Mittel) | `e()` entfernt (Doppel-Escaping behoben) |
| `pwa-install-banner.blade.php` | F15 (Mittel) | Translation-Ausgabe sanitisiert |
| `ProfileController.php` | F17 (Niedrig) | DSGVO Path-Prefix-Check |
| `Auth/ResetPasswordController.php` | F18 (Niedrig) | `Password::min(8)->letters()->numbers()` |
### Akzeptierte Restrisiken
- **F05** (MIME-Spoofing): UUID-Dateinamen + privater Storage + Autorisierung mitigieren das Risiko
- **F20** (EventTimekeeper Enum): CateringStatus und TimekeeperStatus haben identische Werte
- **CSP unsafe-inline/eval**: CDN-Abhaengigkeit (Tailwind/Alpine.js/Quill.js) erfordert dies — langfristig lokale Assets empfohlen