From 3235e1c86761d842b05e1a0eab35f8c2ccf96f02 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 16 Apr 2026 17:30:25 +0200 Subject: [PATCH] Expand and rebuild Passport 13 views. --- .../Controllers/Profile/OAuthController.php | 69 ++- app/Http/Controllers/ProfileController.php | 3 +- config/translations.php | 1 + .../v1/src/components/passport/Clients.vue | 11 +- .../passport/PersonalAccessTokens.vue | 515 +++++++++--------- resources/lang/en_US/firefly.php | 8 +- routes/web.php | 22 + 7 files changed, 371 insertions(+), 258 deletions(-) diff --git a/app/Http/Controllers/Profile/OAuthController.php b/app/Http/Controllers/Profile/OAuthController.php index ac2bb7a65f..774d7e11b2 100644 --- a/app/Http/Controllers/Profile/OAuthController.php +++ b/app/Http/Controllers/Profile/OAuthController.php @@ -23,17 +23,27 @@ namespace FireflyIII\Http\Controllers\Profile; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Laravel\Passport\ClientRepository; +use Laravel\Passport\Token; +use Laravel\Passport\TokenRepository; class OAuthController extends Controller { protected bool $internalAuth; - public function __construct() + + public function __construct(protected TokenRepository $tokenRepository,protected ValidationFactory $validation) { parent::__construct(); $this->middleware(static function ($request, $next) { - app('view')->share('title', (string) trans('firefly.oauth_tokens')); + app('view')->share('title', (string)trans('firefly.oauth_tokens')); app('view')->share('mainTitleIcon', 'fa-user'); return $next($request); @@ -45,7 +55,60 @@ class OAuthController extends Controller $this->middleware(IsDemoUser::class)->except(['index']); } - public function index() { + public function index() + { + $count = DB::table('oauth_clients')->where('grant_types', '["personal_access"]')->whereNull('owner_id')->count(); + + if (0 === $count) { + /** @var ClientRepository $repository */ + $repository = app(ClientRepository::class); + $repository->createPersonalAccessGrantClient('Firefly III Personal Access Grant Client', null); + } + return view('profile.oauth.index'); } + + public function listClients(): JsonResponse + { + // Retrieving all the OAuth app clients that belong to the user... + $clients = auth()->user()->oauthApps()->get(); + return response()->json($clients); + } + + public function storePersonalAccessToken(Request $request): JsonResponse + { + $this->validation->make($request->all(), [ + 'name' => ['required', 'max:255']])->validate(); + + return response()->json($request->user()->createToken($request->name)); + } + + public function destroyPersonalAccessToken(Request $request, string $tokenId): Response + { + $token = $this->tokenRepository->findForUser( + $tokenId, $request->user() + ); + + if (is_null($token)) { + return new Response('', 404); + } + + $token->revoke(); + + return new Response('', Response::HTTP_NO_CONTENT); + } + + public function listPersonalAccessTokens(): JsonResponse + { + // Retrieving all the OAuth app clients that belong to the user... + $tokens = auth()->user()->tokens() + ->with('client') + ->where('revoked', false) + ->where('expires_at', '>', Date::now()) + ->get() + ->filter(fn(Token $token) => $token->client->hasGrantType('personal_access')); + return response()->json($tokens); + } + + } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index c764095bc3..d8cd15abce 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -38,6 +38,7 @@ use FireflyIII\Support\Http\Controllers\CreateStuff; use FireflyIII\User; use Illuminate\Auth\AuthenticationException; use Illuminate\Contracts\Auth\Guard; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -67,7 +68,7 @@ final class ProfileController extends Controller /** * ProfileController constructor. */ - public function __construct() + public function __construct(protected ValidationFactory $validation,) { parent::__construct(); diff --git a/config/translations.php b/config/translations.php index 2bb39e82d9..6c6fcb857d 100644 --- a/config/translations.php +++ b/config/translations.php @@ -139,6 +139,7 @@ return [ ], 'v1' => [ 'firefly' => [ + 'explain_pats', 'administrations_page_title', 'administrations_index_menu', 'expires_at', diff --git a/resources/assets/v1/src/components/passport/Clients.vue b/resources/assets/v1/src/components/passport/Clients.vue index c28ba672e2..17e8a776e5 100644 --- a/resources/assets/v1/src/components/passport/Clients.vue +++ b/resources/assets/v1/src/components/passport/Clients.vue @@ -37,12 +37,15 @@
-

- {{ $t('firefly.profile_oauth_no_clients') }} -

-

+

+ {{ $t('firefly.profile_oauth_clients_explain') }} +

+

{{ $t('firefly.profile_oauth_clients_external_auth') }}

+

+ {{ $t('firefly.profile_oauth_no_clients') }} +

diff --git a/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue b/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue index e6b7615ab4..8dd9a9b1c9 100644 --- a/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue +++ b/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue @@ -20,305 +20,326 @@ diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index e34b6592c7..b9f19ed373 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1639,9 +1639,11 @@ return [ 'delete_local_info_only' => "Because Firefly III isn't responsible for user management or authentication handling, this function will only delete local Firefly III information.", 'oauth' => 'OAuth', 'oauth_tokens' => 'Remote access and tokens', - 'profile_oauth_clients' => 'OAuth Clients', - 'profile_oauth_no_clients' => 'You have not created any OAuth clients.', - 'profile_oauth_clients_external_auth' => 'If you\'re using an external authentication provider like Authelia, OAuth Clients will not work. You can use Personal Access Tokens only.', + 'profile_oauth_clients' => 'OAuth Clients and Applications', + 'explain_pats' => 'Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.', + 'profile_oauth_no_clients' => 'You have not created any OAuth clients or applications.', + 'profile_oauth_clients_external_auth' => 'Please note that if you\'re using an external authentication provider like Authelia, OAuth Clients will not work. You can use Personal Access Tokens only.', + 'profile_oauth_clients_explain' => 'An OAuth client can be used to connect "smart" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a "secret". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.', 'profile_oauth_clients_header' => 'Clients', 'profile_oauth_client_id' => 'Client ID', 'profile_oauth_client_name' => 'Name', diff --git a/routes/web.php b/routes/web.php index 6ec8fb7b6b..2d8d39074f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -29,6 +29,28 @@ if (!defined('DATEFORMAT')) { define('DATEFORMAT', '(19|20)[0-9]{2}-?[0-9]{2}-?[0-9]{2}'); } +// new Passport routes. +Route::group( + [ + 'as' => 'passport.', + 'prefix' => 'oauth', + // 'namespace' => 'FireflyIII\Http\Controllers\OAuth', + ], + function (): void { + // routes with no extra middleware + // Route::post('/token', ['uses' => '\Laravel\Passport\Http\Controllers\AccessTokenController@issueToken', 'as' => 'token', 'middleware' => 'throttle']); + // Route::get('/authorize', ['uses' => 'AuthorizationController@authorize', 'as' => 'authorizations.authorize', 'middleware' => 'user-full-auth']); + + // personal access tokens: + Route::post('/personal-access-tokens', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@storePersonalAccessToken', 'as' => 'personal.tokens.store']); + Route::get('/personal-access-tokens', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@listPersonalAccessTokens', 'as' => 'personal.tokens.index']); + Route::delete('/personal-access-tokens/{token_id}', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@destroyPersonalAccessToken', 'as' => 'personal.tokens.destroy']); + + // clients: + Route::get('/clients', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@listClients', 'as' => 'clients.index']); + } +); + // laravel passport routes //Route::group( // [
{{ $t('firefly.profile_oauth_clients_header') }}