From e557d37b9ee53d4e06b6dee1776c89a2d4eb3bbc Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 29 Jun 2026 06:13:28 -0600 Subject: [PATCH] Fix PAT --- .../CachedPersonalAccessClientRepository.php | 130 ++++++++++++++++++ app/Providers/PassportServiceProvider.php | 9 ++ routes/web.php | 6 +- 3 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 app/Passport/CachedPersonalAccessClientRepository.php diff --git a/app/Passport/CachedPersonalAccessClientRepository.php b/app/Passport/CachedPersonalAccessClientRepository.php new file mode 100644 index 000000000..09c068664 --- /dev/null +++ b/app/Passport/CachedPersonalAccessClientRepository.php @@ -0,0 +1,130 @@ +cacheKey($provider); + + $cachedClientId = Cache::get($cacheKey); + + if ($cachedClientId) { + $client = $this->findValidClientById($cachedClientId, $provider); + + if ($client) { + return $client; + } + + Cache::forget($cacheKey); + } + + $client = $this->discoverPersonalAccessClient($provider); + + Cache::forever($cacheKey, $client->getKey()); + + return $client; + } + + private function findValidClientById(string|int $clientId, string $provider): ?Client + { + $client = Passport::client() + ->newQuery() + ->whereKey($clientId) + ->where('revoked', false) + ->first(); + + if (! $client) { + return null; + } + + if (! $this->clientMatchesProvider($client, $provider)) { + return null; + } + + if (! $client->hasGrantType('personal_access')) { + return null; + } + + return $client; + } + + private function discoverPersonalAccessClient(string $provider): Client + { + $model = Passport::client(); + + $columns = $model + ->getConnection() + ->getSchemaBuilder() + ->getColumnListing($model->getTable()); + + $hasGrantTypesColumn = in_array('grant_types', $columns, true); + $hasLegacyPersonalAccessColumn = in_array('personal_access_client', $columns, true); + + if (! $hasGrantTypesColumn && ! $hasLegacyPersonalAccessColumn) { + throw new RuntimeException( + 'Unable to discover Passport personal access client: missing grant_types and personal_access_client columns.' + ); + } + + $query = $model + ->newQuery() + ->where('revoked', false) + ->where(function (Builder $query) use ($provider): void { + $query + ->when($provider === config('auth.guards.api.provider'), function (Builder $query): void { + $query->orWhereNull('provider'); + }) + ->orWhere('provider', $provider); + }) + ->where(function (Builder $query) use ($hasGrantTypesColumn, $hasLegacyPersonalAccessColumn): void { + if ($hasGrantTypesColumn) { + $query->orWhere('grant_types', 'like', '%"personal_access"%'); + } + + if ($hasLegacyPersonalAccessColumn) { + $query->orWhere('personal_access_client', true); + } + }); + + $client = $query + ->latest('created_at') + ->first(); + + if (! $client) { + throw new RuntimeException( + "Personal access client not found for [{$provider}] user provider. Please create one with passport:client --personal." + ); + } + + if (! $client->hasGrantType('personal_access')) { + throw new RuntimeException( + "Discovered Passport client [{$client->getKey()}] does not have the personal_access grant." + ); + } + + return $client; + } + + private function clientMatchesProvider(Client $client, string $provider): bool + { + return $client->provider === $provider + || ( + $client->provider === null + && $provider === config('auth.guards.api.provider') + ); + } + + private function cacheKey(string $provider): string + { + return 'pf:passport:personal-access-client-id:'.$provider; + } +} diff --git a/app/Providers/PassportServiceProvider.php b/app/Providers/PassportServiceProvider.php index 2fa0394e0..a9e17e8fb 100644 --- a/app/Providers/PassportServiceProvider.php +++ b/app/Providers/PassportServiceProvider.php @@ -2,13 +2,22 @@ namespace App\Providers; +use App\Passport\CachedPersonalAccessClientRepository; use Laravel\Passport\Bridge; +use Laravel\Passport\ClientRepository; use Laravel\Passport\Passport; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; class PassportServiceProvider extends \Laravel\Passport\PassportServiceProvider { + public function register(): void + { + parent::register(); + + $this->app->singleton(ClientRepository::class, CachedPersonalAccessClientRepository::class); + } + /** * Make the authorization service instance. */ diff --git a/routes/web.php b/routes/web.php index 48b4a9482..645d52026 100644 --- a/routes/web.php +++ b/routes/web.php @@ -64,12 +64,10 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('/authorize', [ 'uses' => '\Laravel\Passport\Http\Controllers\AuthorizationController@authorize', 'as' => 'authorizations.authorize', - 'middleware' => ['web', 'throttle:10,1'] + 'middleware' => ['web', 'throttle:10,1'], ]); - $guard = config('passport.guard', null); - - Route::middleware(['web', $guard ? 'auth:'.$guard : 'auth', 'validemail'])->group(function () { + Route::middleware(['web', 'auth:web', 'validemail'])->group(function () { Route::post('/token/refresh', [ 'uses' => '\Laravel\Passport\Http\Controllers\TransientTokenController@refresh', 'as' => 'token.refresh',