From 933e24c9bade0d1f4aac3ed0f4e3411aec2f7bb7 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Wed, 16 Sep 2026 02:55:25 -0600 Subject: [PATCH] Improve oAuth authorize screen, and only assign admin scopes to admin accounts --- app/Models/User.php | 14 +- app/Passport/ScopeRepository.php | 47 +++ app/Providers/PassportServiceProvider.php | 3 +- .../views/auth/oauth/authorize.blade.php | 374 +++++++++++++++--- 4 files changed, 371 insertions(+), 67 deletions(-) create mode 100644 app/Passport/ScopeRepository.php diff --git a/app/Models/User.php b/app/Models/User.php index bb6db4489..6b65ad7e1 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -20,6 +20,7 @@ class User extends Authenticatable implements OAuthenticatable protected function casts(): array { return [ + 'is_admin' => 'boolean', 'deleted_at' => 'datetime', 'email_verified_at' => 'datetime', '2fa_setup_at' => 'datetime', @@ -41,9 +42,16 @@ class User extends Authenticatable implements OAuthenticatable * @var array */ protected $hidden = [ - 'email', 'password', 'is_admin', 'remember_token', - 'email_verified_at', '2fa_enabled', '2fa_secret', - '2fa_backup_codes', '2fa_setup_at', 'deleted_at', + 'email', + 'password', + 'is_admin', + 'remember_token', + 'email_verified_at', + '2fa_enabled', + '2fa_secret', + '2fa_backup_codes', + '2fa_setup_at', + 'deleted_at', 'updated_at', ]; diff --git a/app/Passport/ScopeRepository.php b/app/Passport/ScopeRepository.php new file mode 100644 index 000000000..f4114f539 --- /dev/null +++ b/app/Passport/ScopeRepository.php @@ -0,0 +1,47 @@ +is_admin) { + return $scopes; + } + + // No user (client_credentials) or a non-admin user: never issue admin scopes. + return collect($scopes) + ->reject(fn ($scope) => self::isAdminScope($scope->getIdentifier())) + ->values() + ->all(); + } +} diff --git a/app/Providers/PassportServiceProvider.php b/app/Providers/PassportServiceProvider.php index a9e17e8fb..6e588e49c 100644 --- a/app/Providers/PassportServiceProvider.php +++ b/app/Providers/PassportServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use App\Passport\CachedPersonalAccessClientRepository; +use App\Passport\ScopeRepository; use Laravel\Passport\Bridge; use Laravel\Passport\ClientRepository; use Laravel\Passport\Passport; @@ -26,7 +27,7 @@ class PassportServiceProvider extends \Laravel\Passport\PassportServiceProvider return tap(new AuthorizationServer( $this->app->make(Bridge\ClientRepository::class), $this->app->make(Bridge\AccessTokenRepository::class), - $this->app->make(Bridge\ScopeRepository::class), + $this->app->make(ScopeRepository::class), $this->makeCryptKey('private'), Passport::tokenEncryptionKey($this->app->make('encrypter')), $responseType ?? Passport::$authorizationServerResponseType diff --git a/resources/views/auth/oauth/authorize.blade.php b/resources/views/auth/oauth/authorize.blade.php index e5355b119..a87f0da5e 100644 --- a/resources/views/auth/oauth/authorize.blade.php +++ b/resources/views/auth/oauth/authorize.blade.php @@ -1,5 +1,6 @@ + @@ -7,93 +8,340 @@ {{ config_cache('app.name') }} - Authorization + -
-
-
-
- + @php + $user = request()->user(); + + $scopeIcons = [ + 'read' => 'fas fa-eye', + 'write' => 'fas fa-pen', + 'follow' => 'fas fa-user-plus', + 'push' => 'fas fa-bell', + 'admin:read' => 'fas fa-server', + 'admin:read:domain_blocks' => 'fas fa-globe', + 'admin:write' => 'fas fa-tools', + 'admin:write:domain_blocks' => 'fas fa-gavel', + 'security:read' => 'fas fa-shield-alt', + 'security:write' => 'fas fa-key', + ]; + + $requestedScopes = collect($scopes); + $grantableScopes = $user->is_admin + ? $requestedScopes + : $requestedScopes->reject(fn ($scope) => \App\Passport\ScopeRepository::isAdminScope($scope->id)); + $droppedAdminScopes = $requestedScopes->count() - $grantableScopes->count(); + + $scopeLevel = function ($id) { + if (\App\Passport\ScopeRepository::isAdminScope($id)) { + return 'danger'; + } + if (in_array($id, ['write', 'security:write'])) { + return 'warning'; + } + return 'default'; + }; + + $levelRank = ['danger' => 0, 'warning' => 1, 'default' => 2]; + $sortedScopes = $grantableScopes + ->sortBy(fn ($scope) => $levelRank[$scopeLevel($scope->id)]) + ->values(); + + $visibleCount = 3; + $hiddenCount = max($sortedScopes->count() - $visibleCount, 0); + $moreLabel = 'Show ' . $hiddenCount . ' more ' . \Illuminate\Support\Str::plural('permission', $hiddenCount); + @endphp + +
+
+ {{ config_cache('app.name') }} +
+ +
+
+ -

Authorize {{ $client->name }}

-
-
-
- -
-

{{ $client->name }}

-

wants access to your {{request()->user()->username}} account

-
-
-
- @if (count($scopes) > 0) -
- @foreach ($scopes as $scope) -
- -
-

{{ $scope->id }}

-

{{$scope->description}}

-
-
- @endforeach -
+ +

Authorize {{ $client->name }}

+

+ {{ $client->name }} wants to access your + {{ '@' . $user->username }} account. +

+ + @if ($sortedScopes->count() > 0) +

This app will be able to:

+ +
+ @foreach ($sortedScopes as $scope) + @if ($loop->index === $visibleCount) +
+ + {{ $moreLabel }} + Show fewer permissions + + @endif -
-
- {{ csrf_field() }} - - - - - -
- -
- {{ csrf_field() }} - {{ method_field('DELETE') }} - - - - - -
+ @php $level = $scopeLevel($scope->id); @endphp +
+
+ +
+
+

{{ $scope->description }}

+ {{ $scope->id }} +
-
-

Click here to log out of this account.

-
+ @endif +
+ @endif + + @if ($droppedAdminScopes > 0) +

+ This app also asked for admin permissions, which don't apply to your account. +

+ @endif + +
+
+ + @csrf + @method('DELETE') + + + + + +
+
+
+ @csrf + + + +
+ +

+ You can revoke this access at any time from your account settings. +

+ +

+ Signed in as {{ '@' . $user->username }}. + Not you? + Log out +

+ +
+ @csrf +
- + + \ No newline at end of file