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

7.4 KiB

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