app/Providers/FortifyServiceProvider.php
Rama main · réplica de lectura del repositorio original.
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Http\Responses\NeutralPasswordResetLinkRequestResponse;
use Artwork\Modules\ExternalUserManagement\Repository\ExternalUserSourceRepository;
use Artwork\Modules\ExternalUserManagement\Service\CredentialLoginService;
use Artwork\Modules\GeneralSettings\Models\GeneralSettings;
use Artwork\Modules\User\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Config\Repository;
use Illuminate\Http\Request;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
use Laravel\Fortify\Contracts\LoginResponse;
class FortifyServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
// @phpstan-ignore-next-line
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
// Einziger Credential-Pfad des Passwortformulars: lokaler Hash für lokale
// Accounts, Bind gegen das Directory für LDAP, Sperre für OIDC (nur SSO).
Fortify::authenticateUsing(function (Request $request) {
return app(CredentialLoginService::class)->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);
}
};
});
}
}