attempt( (string) $request->input(Fortify::username()), (string) $request->input('password') ); }); // Login-View mit aktiven Identity-Provider-Quellen für die OIDC/SSO-Buttons anreichern. Fortify::loginView(function () { return Inertia::render('Auth/Login', [ 'canResetPassword' => Route::has('password.request'), 'status' => session('status'), 'identityProviderSources' => rescue( fn () => app(ExternalUserSourceRepository::class)->getActiveIdentityProviderSourcesForLogin(), collect(), false ), ]); }); RateLimiter::for('login', function (Request $request) { // Läuft vor der Request-Validierung: Bots posten "email" auch als Array $email = $request->input(Fortify::username()); $email = is_string($email) ? $email : ''; return [ Limit::perMinute(5)->by($email . $request->ip()), // Pro Konto unabhängig von der IP: bremst Password-Spraying über viele Adressen. // Höchstens 15 Minuten Wartezeit, damit ein Angreifer ein Konto nicht lange sperren kann. Limit::perMinutes(15, 10)->by('account|' . Str::lower(trim($email))), ]; }); RateLimiter::for('two-factor', function (Request $request) { return Limit::perMinute(5)->by($request->session()->get('login.id')); }); RateLimiter::for('password-reset', function (Request $request) { $email = $request->input(Fortify::email()); $email = is_string($email) ? Str::lower(trim($email)) : ''; return Limit::perMinute(5)->by($email . '|' . $request->ip()); }); // Fortify registriert password.email ohne Throttle; die Route existiert erst nach dem Booten. $this->app->booted(function (): void { Route::getRoutes()->getByName('password.email')?->middleware('throttle:password-reset'); }); // Unbekannte E-Mail antwortet wie der Erfolgsfall (keine Nutzer-Enumeration). $this->app->singleton( FailedPasswordResetLinkRequestResponse::class, NeutralPasswordResetLinkRequestResponse::class ); ResetPassword::toMailUsing( function (User $notifiable, string $token) { /** @var GeneralSettings $settings */ $settings = $this->app->make(GeneralSettings::class); $config = $this->app->make(Repository::class); $fallbackSenderMail = $config->get('mail.system_mail'); $pageTitle = $settings->page_title !== '' ? $settings->page_title : $config->get('mail.fallback_page_title'); return (new MailMessage()) ->from( $settings->business_email !== '' ? $settings->business_email : $fallbackSenderMail, $pageTitle ) ->subject(__('Reset password')) ->markdown( 'emails.password_reset', [ 'name' => $notifiable->first_name . ' ' . $notifiable->last_name, 'page_title' => $settings->page_title !== '' ? $settings->page_title : 'Artwork', 'url' => sprintf( '%s/reset-password/%s?email=%s', $config->get('app.url'), $token, urlencode($notifiable->email) ) ] ); } ); // Custom Login Response für Inertia.js - erzwingt Full-Page-Reload nach Login $this->app->singleton(LoginResponse::class, function () { return new class implements LoginResponse { public function toResponse($request) { $intended = redirect()->intended(RouteServiceProvider::HOME)->getTargetUrl(); // Für Inertia-Requests: Force Full-Page-Reload if ($request->header('X-Inertia')) { return response('', 409) ->header('X-Inertia-Location', $intended); } return redirect()->intended(RouteServiceProvider::HOME); } }; }); } }