diff --git a/src/Actions/AttemptToAuthenticate.php b/src/Actions/AttemptToAuthenticate.php new file mode 100644 index 0000000..2174f68 --- /dev/null +++ b/src/Actions/AttemptToAuthenticate.php @@ -0,0 +1,58 @@ +handleUsingCustomCallback($request, $next); + } + + // Fallback to Laravel Fortify + if (! $request->route('provider') && $request->route(Fortify::username())) { + return parent::handle($request, $next); + } + + if ($authIdentifier) { + $this->guard->loginUsingId($authIdentifier, Socialstream::hasRememberSessionFeatures()); + + return $next($request); + } + + $socialUser = $this->resolver->resolve($request->route('provider')); + + $connectedAccount = tap(Socialstream::$connectedAccountModel::where('email', $socialUser->getEmail())->first(), function ($connectedAccount) use ($request, $socialUser) { + if (! $connectedAccount) { + event(new Failed($this->guard?->name ?? config('fortify.guard'), user: null, credentials: [ + 'provider' => $request->route('provider'), + ])); + + $this->throwFailedAuthenticationException($request); + } + }); + + $this->guard->login($connectedAccount->user, Socialstream::hasRememberSessionFeatures()); + + return $next($request); + } +} diff --git a/src/Actions/AuthenticateOAuthCallback.php b/src/Actions/AuthenticateOAuthCallback.php index 8f9eacf..055affe 100644 --- a/src/Actions/AuthenticateOAuthCallback.php +++ b/src/Actions/AuthenticateOAuthCallback.php @@ -31,9 +31,11 @@ use JoelButcher\Socialstream\Features; use JoelButcher\Socialstream\Providers; use JoelButcher\Socialstream\Socialstream; +use Laravel\Fortify\Actions\AttemptToAuthenticate as FortifyAttemptToAuthenticate; use Laravel\Fortify\Actions\CanonicalizeUsername; use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled; use Laravel\Fortify\Actions\PrepareAuthenticatedSession; +use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable as FortifyRedirectIfTwoFactorAuthenticatable; use Laravel\Fortify\Features as FortifyFeatures; use Laravel\Fortify\Fortify; use Laravel\Jetstream\Jetstream; @@ -158,11 +160,10 @@ protected function login(Authenticatable $user, mixed $account, string $provider protected function loginPipeline(Request $request, Authenticatable $user): Pipeline { - if (!class_exists(Fortify::class)) { + if (! class_exists(Fortify::class)) { return (new Pipeline(app()))->send($request)->through(array_filter([ - function ($request, $next) use ($user) { - $this->guard->login($user, Socialstream::hasRememberSessionFeatures()); - + AttemptToAuthenticate::class.':'.$user->getAuthIdentifier(), + function ($request, $next) { if ($request->hasSession()) { $request->session()->regenerate(); } @@ -173,26 +174,22 @@ function ($request, $next) use ($user) { } if (Fortify::$authenticateThroughCallback) { - return (new Pipeline(app()))->send($request)->through(array_filter( + return (new Pipeline(app()))->send($request)->through($this->replaceFortifyAuthPipes(array_filter( call_user_func(Fortify::$authenticateThroughCallback, $request) - )); + ))); } if (is_array(config('fortify.pipelines.login'))) { - return (new Pipeline(app()))->send($request)->through(array_filter( + return (new Pipeline(app()))->send($request)->through($this->replaceFortifyAuthPipes(array_filter( config('fortify.pipelines.login') - )); + ))); } return (new Pipeline(app()))->send($request)->through(array_filter([ config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class, config('fortify.lowercase_usernames') ? CanonicalizeUsername::class : null, FortifyFeatures::enabled(FortifyFeatures::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null, - function ($request, $next) use ($user) { - $this->guard->login($user, Socialstream::hasRememberSessionFeatures()); - - return $next($request); - }, + AttemptToAuthenticate::class.':'.$user->getAuthIdentifier(), PrepareAuthenticatedSession::class, ])); } @@ -298,4 +295,19 @@ private function canRegister(): bool return Features::hasCreateAccountOnFirstLoginFeatures() && Features::hasGlobalLoginFeatures(); } + + private function replaceFortifyAuthPipes(mixed $pipes): array + { + return array_map(function ($pipe) { + if ($pipe === FortifyAttemptToAuthenticate::class) { + return AttemptToAuthenticate::class; + } + + if ($pipe === FortifyRedirectIfTwoFactorAuthenticatable::class) { + return RedirectIfTwoFactorAuthenticatable::class; + } + + return $pipe; + }, $pipes); + } } diff --git a/src/Actions/RedirectIfTwoFactorAuthenticatable.php b/src/Actions/RedirectIfTwoFactorAuthenticatable.php index 2896763..2c0d9fe 100644 --- a/src/Actions/RedirectIfTwoFactorAuthenticatable.php +++ b/src/Actions/RedirectIfTwoFactorAuthenticatable.php @@ -4,13 +4,25 @@ namespace JoelButcher\Socialstream\Actions; +use Illuminate\Auth\Events\Failed; +use Illuminate\Contracts\Auth\StatefulGuard; use JoelButcher\Socialstream\Contracts\ResolvesSocialiteUsers; use JoelButcher\Socialstream\Socialstream; use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable as BaseAction; use Laravel\Fortify\Fortify; +use Laravel\Fortify\LoginRateLimiter; class RedirectIfTwoFactorAuthenticatable extends BaseAction { + public function __construct( + StatefulGuard $guard, + LoginRateLimiter $limiter, + protected ResolvesSocialiteUsers $resolver + ) { + parent::__construct($guard, $limiter); + + } + protected function validateCredentials($request) { if (Fortify::$authenticateUsingCallback) { @@ -23,12 +35,18 @@ protected function validateCredentials($request) }); } - $socialUser = app(ResolvesSocialiteUsers::class) - ->resolve($request->route('provider')); + // Fallback to Laravel Fortify + if (! $request->route('provider') && $request->route(Fortify::username())) { + return parent::validateCredentials($request); + } + + $socialUser = $this->resolver->resolve($request->route('provider')); $connectedAccount = tap(Socialstream::$connectedAccountModel::where('email', $socialUser->getEmail())->first(), function ($connectedAccount) use ($request, $socialUser) { if (! $connectedAccount) { - $this->fireFailedEvent($request, $connectedAccount->user); + event(new Failed($this->guard?->name ?? config('fortify.guard'), user: null, credentials: [ + 'provider' => $request->route('provider'), + ])); $this->throwFailedAuthenticationException($request); } diff --git a/stubs/app/Providers/SocialstreamServiceProvider.php b/stubs/app/Providers/SocialstreamServiceProvider.php index 41e2ee0..4c24a43 100644 --- a/stubs/app/Providers/SocialstreamServiceProvider.php +++ b/stubs/app/Providers/SocialstreamServiceProvider.php @@ -11,7 +11,6 @@ use Illuminate\Support\ServiceProvider; use JoelButcher\Socialstream\Concerns\ConfirmsFilament; use JoelButcher\Socialstream\Socialstream; -use Laravel\Fortify\Fortify; class SocialstreamServiceProvider extends ServiceProvider {