Befund (Review 16.06.): Der Volt-Login machte direkt Auth::attempt() und umging Fortifys 2FA-Pipeline (2FA-Bypass); zusätzlich existierte der Fortify-POST /login parallel mit schwächeren Post-Login-Regeln. Fix (Volt-nativ): - Volt-Login prüft Credentials ohne sofortiges Login; bei aktivem 2FA wird der Session-Vertrag login.id/login.remember gesetzt und auf eine neue Volt- 2FA-Challenge-Seite (/two-factor-challenge) geleitet, die an Fortifys bestehenden Controller postet (TOTP + Recovery-Code). - Gemeinsame Post-Login-Logik in App\Support\LoginRedirect (rollengerechtes Home + 403-sicherer intended-Redirect), genutzt von Volt-Login UND Response. - RoleAwareLoginResponse implementiert jetzt LoginResponse UND TwoFactorLoginResponse und erzwingt einheitlich: unverifiziert → Notice, verifiziert-inaktiv → Logout+Fehler, sonst 403-sicherer Redirect. Damit ist auch der direkte Fortify-POST-Pfad gehärtet. Tests: 2FA-Übergabe, Challenge-Guard, voller TOTP-Flow, Fortify-POST blockt inaktive User und hält Customer aus dem Admin-Bereich. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8 KiB
Version: Juni 2026 · Datum: 16.06.2026 · Status: Umgesetzt Scope: Sicherheits- und deployment-kritische Punkte aus der Auth-/Rollen-/Verifizierungs-Phase (WS-6 & WS-2). Dieses Dokument bündelt, was beim Go-Live zwingend beachtet werden muss. Feed für die spätere Live-Deployment-Checkliste (WS-7).
1. ⚠️ SICHERHEIT — Legacy-Rollen-Überberechtigung (behoben)
Befund: Der Legacy-Import (app/Services/Import/UserImporter.php) mappte die Alt-Gruppe 2 auf die neue Rolle editor. Die Alt-Gruppe 2 war jedoch die normale Self-Publisher-Masse (65.950 von 66.151 Usern) – keine Redaktions-Mitarbeiter. Im neuen Rollenmodell gewährt editor Admin-Panel-Zugriff (User::canAccessAdmin() prüft hasRole(['admin','editor'])).
Auswirkung: Praktisch jeder Legacy-Kunde konnte sich – insbesondere über den Magic-Link-Login – ins komplette Admin-Panel einloggen.
Fix:
- Mapping korrigiert: Alt-Gruppe 2 →
customer(statteditor).editorwird ab jetzt ausschließlich manuell an echtes Personal vergeben. - Daten-Migration
2026_06_16_080913_downgrade_legacy_editor_users_to_customerstuft die bestehenden Legacy-editor(alle mitlegacy_portal) aufcustomerherab. Chunked (Placeholder-Limit), nicht umkehrbar. - Ergebnis nach Migration:
editor = 0,customer ≈ 65.952.
Deployment / Nachkontrolle:
- Migration MUSS auf Prod laufen.
- Es bleiben 4 Legacy-Admins (Alt-Gruppe 1) – manuell prüfen, ob das wirklich Personal ist.
- Kontrolle nach Deploy:
Spatie\Permission\Models\Role::findByName('editor')->users()->whereNotNull('legacy_portal')->count()muss 0 sein.
2. ⚠️ DEPLOYMENT-KRITISCH — E-Mail-Verifizierung scharf
User implementiert jetzt MustVerifyEmail; die verified-Middleware auf den Panel-Routen greift damit real (war vorher wirkungslos).
Lockout-Gefahr: Ohne Vorsorge würden ALLE Bestands-User mit email_verified_at = NULL (alle 66.151, davon ~59.403 aktiv) ausgesperrt.
Absicherung: Migration 2026_06_15_101337_backfill_email_verified_at_for_existing_users setzt email_verified_at für alle bestehenden User.
Deployment: Diese Migration MUSS mit dem Deploy laufen, bevor echter Traffic auf die geschützten Routen kommt. Neue Registrierungen (nach Deploy) bleiben absichtlich NULL, bis verifiziert.
Registrierungs-Verhalten (Entscheidung 15.06.):
- Selbst-Registrierung legt ein inaktives, rollenloses Konto an → Danke-/Notice-Seite → Verifizierungsmail.
- Bestätigter Link →
is_active = true+customer-Rolle (App\Listeners\ActivateUserAfterVerification). - Ausnahme-Kanäle (gelten als verifiziert, kein E-Mail-Schritt): Magic-Link, Kontaktzugang (WS-2), Google-Login (geplant).
3. VERHALTENSÄNDERUNG — Firmen-Scope für Pressemitteilungen
PM-Zugriff war hart an user_id (Autor) gebunden. Jetzt additiv: Zugriff = Autor ODER Mitglied der zugeordneten Firma (Owner via owner_user_id oder company_user-Pivot).
- Betroffen:
PressReleasePolicy::canManage()(view/update/submitForReview/delete) sowie die Queries der Kunden-Komponenten (Liste/Show/Edit) undUser::accessibleCompanyIds(). - Solo-Owner: keine sichtbare Änderung (sie sind Autor ihrer PMs).
- Firmen mit mehreren Mitgliedern: Mitglieder sehen/bearbeiten jetzt alle PMs der Firma (gewollt – „gesamte Firma").
4. Magic-Link-Zugang für Pressekontakte (WS-2)
Öffentliches Formular /pressekontakt-zugang (auth.contact-access):
- Enumeration-sicher (neutrale Antwort unabhängig vom Treffer), Honeypot + Rate-Limit.
- Eine hinterlegte Kontakt-E-Mail → lazy angelegter, per E-Mail de-duplizierter
customer-Account (aktiv, verifiziert), der den Firmen seiner Kontakte als Mitglied zugeordnet wird. - Versand über den bestehenden Login-Magic-Link (
MagicLinkGenerator+MagicLinkConsumeController) – keine Schema-Änderung.
Design-Nuance (bewusst): Der Account wird beim Absenden der Anfrage angelegt (nicht erst beim Klick), um eine fragile magic_links-Schema-Änderung (ENUM/FK über SQLite-Tests + MySQL-Prod) zu vermeiden. Er ist „lazy" (nur für real existierende Kontakt-Mails) und ohne den per Mail versendeten Token wertlos.
5. Guest-Redirect rollenbewusst (403-Sackgasse behoben)
bootstrap/app.php → redirectUsersTo: eingeloggte User auf Gast-Routen (/login, /register) werden rollen-/verifizierungsbewusst geleitet (Admin → /dashboard, Customer → /admin/me, unverifiziert → Notice). Verhindert, dass ein Customer auf das feste /dashboard (403) läuft und festsitzt.
Ergänzend: Magic-Link-Consume nutzt einen rollensicheren Redirect ohne intended() (eine als Gast besuchte Admin-URL darf einen Customer nicht in den 403-Bereich schicken).
6. Login-/Magic-Link-Härtung (Security-Review 16.06.)
Aus einer gezielten Auth-Prüfung umgesetzt:
- Magic-Link-Versand rate-limited (vorher ungedrosselt): pro E-Mail+IP (3/h) und zusätzlich pro IP (15/h) – verhindert Mail-Fluten aktiver Accounts und das laufende Entwerten alter Links. Der Pressekontakt-Zugang war bereits limitiert.
- Inaktive (aber verifizierte) User werden beim Passwort-Login zentral blockiert (
Auth::logout()+ Fehler), analog zum Magic-Link-Consume. Schließt nur-auth+verified-Routen ohne zusätzlichenis_active-Check ab. - Rollensicherer Login-Redirect: eine gemerkte
intended-Admin-URL (z. B./admin/users) schickt einen Customer nicht mehr in den 403 – inaktzessible Admin-Pfade fallen auf das rollengerechte Ziel zurück. - ContactAccess mutiert keine deaktivierten Bestands-Accounts mehr: der
is_active-Check läuft vor jeder Firmenzuordnung/jedem Linkversand. - Magic-Link-Verbrauch ist atomar (konditionales
UPDATE … whereNull(consumed_at)): Single-Use auch bei parallelen Requests garantiert.
Captcha (dokumentiert, vorerst NICHT eingebaut): Beim Pressekontakt-Zugang greifen aktuell Honeypot + Rate-Limit. Ein echtes Captcha (Cloudflare Turnstile / hCaptcha) ist sinnvoll, wird aber erst eingebaut, falls im Livebetrieb zu viel Missbrauch auftritt (Entscheidung 16.06.) – braucht Provider-Entscheidung, Keys und ein Paket.
6b. 2FA-Bypass behoben & Login-Pfade konsolidiert (Review 16.06., Teil 2)
Befund: Der sichtbare Volt-Login machte direkt Auth::attempt() und umging damit Fortifys 2FA-Pipeline (RedirectIfTwoFactorAuthenticatable) – für Accounts mit aktivem 2FA faktisch ein 2FA-Bypass. Zusätzlich existierte der Fortify-POST /login parallel mit schwächeren Post-Login-Regeln (kein is_active-Block, resolveTarget neutralisierte nur / und /dashboard).
Fix (Volt-nativ):
- Der Volt-Login prüft die Zugangsdaten jetzt OHNE sofortiges Einloggen; bei aktivem 2FA (
hasEnabledTwoFactorAuthentication()) wird der Session-Vertraglogin.id/login.remembergesetzt und auf eine neue Volt-2FA-Challenge-Seite (/two-factor-challenge) geleitet. Diese postet an Fortifys bestehendentwo-factor-challenge-Controller (TOTP + Recovery-Code). - Gemeinsame Post-Login-Logik in
App\Support\LoginRedirect(rollengerechtes Home + 403-sicherer intended-Redirect), genutzt von Volt-Login UND der Fortify-Response. RoleAwareLoginResponseimplementiert jetzt beide Contracts (LoginResponseundTwoFactorLoginResponse) und erzwingt einheitlich: unverifiziert → Notice, verifiziert-inaktiv → Logout+Fehler, sonst 403-sicherer Redirect. Damit ist auch der direkte Fortify-POST-Pfad gehärtet.
Hinweis: fortify.views => false bleibt; die Challenge wird vom Volt-Frontend bereitgestellt, die Verifizierung übernimmt Fortify.
7. Deployment-Reihenfolge (Migrationen dieser Phase)
2026_06_15_101337_backfill_email_verified_at_for_existing_users— verhindert Lockout.2026_06_16_080913_downgrade_legacy_editor_users_to_customer— schließt die Admin-Überberechtigung.
Nach Deploy prüfen:
editor-Rolle hat keine Legacy-User mehr.- Niemand ungewollt ausgesperrt (Stichprobe aktiver Legacy-User → Login/Panel erreichbar).
- Die 4 verbliebenen Legacy-Admins sind legitimes Personal.