# 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. Deployment: Shared Hosting (all-inkl.com, FTP only, kein SSH). ## 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, SetLocaleMiddleware, SecurityHeadersMiddleware, ActiveUserMiddleware, DsgvoConsentMiddleware, StaffMiddleware, AdminOnlyMiddleware - `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, installer) - `lang/{de,en,pl,ru,ar,tr}/` — 6 Sprachen: Deutsch, Englisch, Polnisch, Russisch, Arabisch, Tuerkisch - `database/migrations/` — Nummeriert mit Prefix 0001-0035 - `database/seeders/` — AdminSeeder, DemoSeeder, FaqSeeder ### 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 - **User-Model**: Nutzt `Notifiable` Trait (erforderlich fuer Passwort-Reset-Notifications) - **.env-Manipulation**: `updateEnvValues()` Helper in InstallerController und SettingsController ### 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) - **Alle Uebersetzungsschluessel muessen in ALLEN 6 Sprachen hinzugefuegt werden** ### Installer (5-Step Wizard) 1. Systemcheck (PHP-Version, Extensions, Berechtigungen) 2. Datenbank (SQLite/MySQL Konfiguration) 3. Einstellungen (App-Name, Admin-Account) 4. E-Mail (SMTP-Konfiguration mit Verbindungstest, oder Log-Modus zum Ueberspringen) 5. Abschluss (Finalize, Demo-Daten, Lizenz) - `InstallerMiddleware` prueft `storage/installed` Datei - Setup-Token-Schutz: Token in `storage/setup-token`, SHA256 im Laravel-Log - SMTP-Test nutzt `Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport` - PW-Reset-Standardtexte fuer 6 Sprachen in `getDefaultPasswordResetTexts()` - Session-basierte Datenpersistenz zwischen Schritten ### Password Reset Flow 1. User klickt "Passwort vergessen?" auf Login-Seite 2. ForgotPasswordController sendet Reset-Link via Laravel Password Broker 3. ResetPasswordNotification laedt Custom-Template aus `Setting::get('password_reset_email_{locale}')` 4. Platzhalter: `{name}`, `{link}`, `{app_name}` 5. Fallback auf Standard-Laravel-Template mit Translation-Keys (`passwords.reset_*`) ### Admin Settings Tabs - **Allgemein**: App-Name, Slogan, Favicon, Saison - **E-Mail**: SMTP-Konfiguration mit Verbindungstest (schreibt in .env, `Artisan::call('config:clear')`) - **Rechtliches**: Impressum + Datenschutz + PW-Reset-E-Mail pro Sprache (Quill-Editor mit Flaggen-Selector) - **Event-Standards**: Min-Werte fuer Spieler, Catering, Zeitnehmer pro Event-Typ - **Dateikategorien**: CRUD fuer File-Categories - **Sichtbarkeit**: Feature-Toggles - **Lizenz**: License-Key-Validierung via SupportApiService - **Wartung**: Demo-Daten loeschen, Factory-Reset ### Lazy Quill-Initialisierung (Rechtliches-Tab) - Quill-Editoren werden erst bei Klick auf die Sprach-Flagge erstellt (`initLocaleEditors(locale)`) - Verhindert Datenverlust: Nicht-initialisierte Locales behalten ihre Server-Werte in Hidden-Inputs - `$watch('legalLocale')` + `$nextTick()` fuer verzoegerte Erstellung - `syncEditors()` synchronisiert nur tatsaechlich initialisierte Editoren ### Route-Struktur (Middleware) - Installer: `throttle:10,1`, ohne SetLocaleMiddleware/ActiveUserMiddleware - Oeffentlich: Login, Register, Legal-Pages, Locale-Switch - User-Bereich: `auth` Middleware - Admin-Bereich: `auth` + `admin` + `prefix('admin')` - Staff-Routes (Admin + Coach): `staff` Middleware - Admin-Only-Routes: `admin-only` Middleware (Settings, Locations, Support) ## Security ### Middleware-Stack - CSP und Permissions-Policy via SecurityHeadersMiddleware (inkl. COOP-Header) - `StaffMiddleware` — prueft Admin oder Coach Rolle - `AdminOnlyMiddleware` — prueft explizit Admin-Rolle (Route-Level-Schutz) - Rate-Limiting auf Auth-Routes, User-Actions, Geocoding, Installer ### Schutzmassnahmen - Honeypot-Feld auf Login/Register-Formularen - DSGVO-Consent-System mit Datei-Upload und Admin-Bestaetigung - Factory Reset benoetigt Passwort + Bestaetigung "RESET-BESTAETIGT" - **File-Autorisierung**: `authorizeFileAccess()` in FileController prueft Team-Zugehoerigkeit - **Settings-Whitelist**: SettingsController akzeptiert nur bekannte Keys + validierte Locale-Suffixe - **SSRF-Schutz**: GeocodingService mit Host-Whitelist, HTTPS-only, DNS-Rebinding-Check, Timeout 5s - **Installer-Sicherheit**: Admin-Passwort sofort gehasht, Setup-Token nur als SHA256 geloggt - **Path-Traversal-Schutz**: DSGVO-Datei-Downloads pruefen `str_starts_with('dsgvo/')` - **HTML-Sanitisierung**: Alle User-Content-Ausgaben durch HtmlSanitizerService geschuetzt - **Invitation-Tokens**: SHA-256 gehasht in DB gespeichert - **E-Mail-Normalisierung**: Lowercase-Vergleich bei Login und Password-Reset - **Team-Scoping**: Coach/ParentRep sehen nur ihre zugewiesenen Teams ### Security-Audit-Historie (Maerz 2026) 6 Audits durchgefuehrt, Score: 6.5 → 9.5/10 - 95 Findings insgesamt, 88 behoben, 3 akzeptierte Risiken, 4 verbleibende Niedrig/Info - Akzeptierte Risiken: MIME-Spoofing (F05), Enum-Reuse (F20), CSP unsafe-inline (W04, CDN-Abhaengigkeit) ## Conventions - Controller-Methoden: resourceful (index, create, store, show, edit, update, destroy) - Blade-Components: ``, ``, ``, `` - Alpine.js fuer interaktive UI-Elemente - Quill.js v1.3.7 fuer WYSIWYG-Editoren (via CDN mit SRI-Hashes) - 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 Whitelist und `updateOrCreate()` behandeln 6. **Wichtig**: Neuen Key zur Whitelist in `SettingsController::update()` hinzufuegen ($allowedLocaleKeys) ### Neue Admin-Route hinzufuegen 1. Route in `routes/web.php` im passenden Middleware-Block: - Alle Admin-Panel-Nutzer: direkt unter `admin` Prefix - Staff (Admin + Coach): unter `staff` Middleware - Nur Admin: unter `admin-only` Middleware 2. Controller-Methode mit Autorisierungs-Pruefung 3. View erstellen 4. Translation-Keys fuer alle 6 Sprachen