Skip to content

Commit

Permalink
[6.x] fix auth when using a custom authentication pipeline in Fortify (
Browse files Browse the repository at this point in the history
  • Loading branch information
joelbutcher authored Oct 3, 2024
1 parent d1c5cde commit 62c5097
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 17 deletions.
58 changes: 58 additions & 0 deletions src/Actions/AttemptToAuthenticate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

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\AttemptToAuthenticate as BaseAction;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;

class AttemptToAuthenticate extends BaseAction
{
public function __construct(
StatefulGuard $guard,
LoginRateLimiter $limiter,
protected ResolvesSocialiteUsers $resolver
) {
parent::__construct($guard, $limiter);
}

public function handle($request, $next, $authIdentifier = null)
{
if (Fortify::$authenticateUsingCallback) {
return $this->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);
}
}
38 changes: 25 additions & 13 deletions src/Actions/AuthenticateOAuthCallback.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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,
]));
}
Expand Down Expand Up @@ -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);
}
}
24 changes: 21 additions & 3 deletions src/Actions/RedirectIfTwoFactorAuthenticatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down
1 change: 0 additions & 1 deletion stubs/app/Providers/SocialstreamServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down

0 comments on commit 62c5097

Please sign in to comment.