'Willkommen zurück', 'eyebrow' => 'Anmeldung im Publisher-Hub', 'topRightLabel' => 'Noch kein Konto?', 'topRightLinkText' => 'Konto erstellen', 'topRightLinkHref' => '/register'])] class extends Component { #[Validate('required|string|email')] public string $email = ''; #[Validate('required|string')] public string $password = ''; public bool $remember = false; /** * Handle an incoming authentication request. */ public function login(): void { $this->validate(); $this->ensureIsNotRateLimited(); // Zugangsdaten prüfen, OHNE schon einzuloggen – sonst würde die // Fortify-2FA-Pipeline umgangen (2FA-Bypass). Legacy-User ohne Passwort // (password = null) scheitern hier korrekt und nutzen Magic-Link/Reset. $user = User::query()->where('email', $this->email)->first(); if (! $user || ! $user->password || ! Hash::check($this->password, $user->password)) { RateLimiter::hit($this->throttleKey()); // Standard-Event wie bei Auth::attempt() — ohne Passwort; der // Aktivitäts-Verlauf leitet den Grund am User ab (Monitoring 1.7). event(new Failed('web', $user, ['email' => $this->email])); throw ValidationException::withMessages([ 'email' => __('auth.failed'), ]); } RateLimiter::clear($this->throttleKey()); // 2FA aktiv: nicht einloggen, sondern in Fortifys Challenge übergeben // (Session-Vertrag login.id/login.remember wie RedirectIfTwoFactorAuthenticatable). if ($user->hasEnabledTwoFactorAuthentication()) { app(LoginCompletion::class)->handOffToTwoFactor(request(), $user, 'password', $this->remember); $this->redirect(route('two-factor.challenge')); return; } Auth::login($user, $this->remember); // Unverifizierte Selbst-Registrierer → Notice-Seite. if (! $user->hasVerifiedEmail()) { Session::regenerate(); $this->redirect(route('verification.notice', absolute: false)); return; } // Verifiziert, aber deaktiviert: Login zentral blockieren. if (! $user->is_active) { Auth::logout(); throw ValidationException::withMessages([ 'email' => __('Ihr Konto ist nicht aktiv. Bitte wenden Sie sich an den Support.'), ]); } app(LoginCompletion::class)->recordLogin($user, request()); Session::regenerate(); // Rollengerechter, 403-sicherer Redirect. Ohne navigate:true, weil das // Portal ein anderes Vite-Bundle nutzt als das Hub-Auth-Layout. $this->redirect(LoginRedirect::safeTarget( $user, Session::pull('url.intended'), LoginRedirect::homeFor($user), )); } /** * Ensure the authentication request is not rate limited. */ protected function ensureIsNotRateLimited(): void { if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { return; } // Die Adresse steckt im Livewire-Payload, nicht im Request-Input — // für den Aktivitäts-Verlauf mitgeben. event(new Lockout(request()->duplicate(request: ['email' => $this->email]))); $seconds = RateLimiter::availableIn($this->throttleKey()); throw ValidationException::withMessages([ 'email' => __('auth.throttle', [ 'seconds' => $seconds, 'minutes' => ceil($seconds / 60), ]), ]); } /** * Get the authentication rate limiting throttle key. */ protected function throttleKey(): string { return Str::transliterate(Str::lower($this->email).'|'.request()->ip()); } }; ?>