'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; // Eigene Eingabe für das Magic-Link-Modal, getrennt vom Login-Formular. public string $magicEmail = ''; /** * 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()); 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()) { Session::put([ 'login.id' => $user->getKey(), 'login.remember' => $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.'), ]); } $user->update([ 'last_login_at' => now(), 'last_login_ip' => request()->ip(), ]); 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), )); } public function sendMagicLink(): void { $this->validate( ['magicEmail' => 'required|string|email'], attributes: ['magicEmail' => __('E-Mail-Adresse')], ); $this->ensureMagicLinkNotRateLimited(); RateLimiter::hit($this->magicLinkThrottleKey(), 3600); RateLimiter::hit($this->magicLinkIpThrottleKey(), 3600); $user = User::query()->where('email', $this->magicEmail)->first(); if ($user && $user->is_active) { $generated = app(MagicLinkGenerator::class)->createLoginLink($user, request()->ip()); $loginUrl = route('magic-links.consume', ['token' => $generated['plain_token']]); Mail::to($user->email)->send( new MagicLoginLink( user: $user, loginUrl: $loginUrl, expiresAt: $generated['expires_at']->format('d.m.Y H:i') ) ); } $this->reset('magicEmail'); $this->dispatch('magic-link-sent'); session()->flash('status', __('If an active account exists for this email, we sent a magic login link.')); } /** * Ensure the authentication request is not rate limited. */ protected function ensureIsNotRateLimited(): void { if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { return; } event(new Lockout(request())); $seconds = RateLimiter::availableIn($this->throttleKey()); throw ValidationException::withMessages([ 'email' => __('auth.throttle', [ 'seconds' => $seconds, 'minutes' => ceil($seconds / 60), ]), ]); } /** * Magic-Link-Versand drosseln: pro E-Mail+IP (gegen Mail-Fluten eines * Accounts und das laufende Entwerten alter Links) und zusätzlich pro IP * (gegen das Durchprobieren vieler Accounts von einer Quelle). */ protected function ensureMagicLinkNotRateLimited(): void { $withinEmail = ! RateLimiter::tooManyAttempts($this->magicLinkThrottleKey(), 3); $withinIp = ! RateLimiter::tooManyAttempts($this->magicLinkIpThrottleKey(), 15); if ($withinEmail && $withinIp) { return; } $seconds = max( RateLimiter::availableIn($this->magicLinkThrottleKey()), RateLimiter::availableIn($this->magicLinkIpThrottleKey()), ); throw ValidationException::withMessages([ 'magicEmail' => __('Zu viele Anfragen. Bitte versuchen Sie es in :minutes Minuten erneut.', [ 'minutes' => max(1, ceil($seconds / 60)), ]), ]); } /** * Get the authentication rate limiting throttle key. */ protected function throttleKey(): string { return Str::transliterate(Str::lower($this->email).'|'.request()->ip()); } protected function magicLinkThrottleKey(): string { return 'magic-link|'.Str::transliterate(Str::lower($this->magicEmail).'|'.request()->ip()); } protected function magicLinkIpThrottleKey(): string { return 'magic-link-ip|'.request()->ip(); } }; ?>
@if (session('status'))
{{ session('status') }}
@endif
@error('email')

{{ $message }}

@enderror
@if (\Illuminate\Support\Facades\Route::has('password.request')) Passwort vergessen? @endif
@error('password')

{{ $message }}

@enderror
oder
Als Pressekontakt hinterlegt? Zugang anfordern →
{{-- Magic-Link-Modal: eigene E-Mail-Eingabe, unabhängig vom Login-Formular --}}