- 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>
7.4 KiB
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, ResetPasswordapp/Http/Middleware/— InstallerMiddleware (Setup-Token), SetLocaleMiddleware, SecurityHeadersMiddleware, ActiveUserMiddleware, DsgvoConsentMiddlewareapp/Services/— HtmlSanitizerService (HTMLPurifier), GeocodingService, SupportApiServiceapp/Enums/— UserRole, EventType, EventStatus, ParticipantStatus, CateringStatusapp/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, Turkischdatabase/migrations/— Nummeriert mit Prefix 0001-0035database/seeders/— AdminSeeder (benotigt ADMIN_EMAIL + ADMIN_PASSWORD in .env)
Wichtige Patterns
- Settings: Key-Value Store in
settingsTabelle.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_pletc. - Passwort-Hashing: User-Model nutzt Laravel Cast
'password' => 'hashed'— KEIN manuelles Hash::make beim Setzen via$user->password = $pwnoetig - HTML-Output: Alle
{!! !!}-Ausgaben von User-Content muessen durchHtmlSanitizerService::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->localeoder Session gesetzt - RTL-Support fuer Arabisch (ar)
Password Reset Flow
- User klickt "Passwort vergessen?" auf Login-Seite
- ForgotPasswordController sendet Reset-Link via Laravel Password Broker
- ResetPasswordNotification laedt optionalen Custom-Template-Text aus
Setting::get('password_reset_email_{locale}') - Platzhalter:
{name},{link},{app_name} - Fallback auf Standard-Laravel-Template mit Translation-Keys (
passwords.reset_*)
Installer
- InstallerMiddleware prueft
storage/installedDatei - Setup-Token-Schutz: Token in
storage/setup-token, wird beim ersten Zugriff generiert und in Laravel-Log geschrieben - Nach Installation wird
storage/installederstellt
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) durchHtmlSanitizerService::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
- Migration erstellen:
Setting::firstOrCreate(['key' => '...'], [...]) - In SettingsController
edit()laden - In View rendern
- In
update()speichern (mit Sanitization fuer HTML)
Neues locale-spezifisches Setting
- Keys:
{setting_name}_{locale}(z.B.impressum_html_de) - Migration fuer alle 6 Locales
- In SettingsController
$localeSettingsArray aufnehmen - In View mit Sprach-Flaggen-Selector anzeigen
- In
update()mitstr_starts_with()undupdateOrCreate()behandeln - 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