diff --git a/app/Http/Controllers/Profile/OAuthController.php b/app/Http/Controllers/Profile/OAuthController.php index 774d7e11b2..2d8e9a511f 100644 --- a/app/Http/Controllers/Profile/OAuthController.php +++ b/app/Http/Controllers/Profile/OAuthController.php @@ -30,6 +30,7 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Laravel\Passport\Client; use Laravel\Passport\ClientRepository; use Laravel\Passport\Token; use Laravel\Passport\TokenRepository; @@ -38,7 +39,7 @@ class OAuthController extends Controller { protected bool $internalAuth; - public function __construct(protected TokenRepository $tokenRepository,protected ValidationFactory $validation) + public function __construct(protected ClientRepository $clients, protected TokenRepository $tokenRepository, protected ValidationFactory $validation) { parent::__construct(); @@ -64,17 +65,93 @@ class OAuthController extends Controller $repository = app(ClientRepository::class); $repository->createPersonalAccessGrantClient('Firefly III Personal Access Grant Client', null); } - - return view('profile.oauth.index'); + $link = route('index'); + return view('profile.oauth.index', compact('link')); } public function listClients(): JsonResponse { // Retrieving all the OAuth app clients that belong to the user... - $clients = auth()->user()->oauthApps()->get(); + $clients = auth()->user()->oauthApps()->where('revoked', false)->get(); return response()->json($clients); } + public function storeClient(Request $request): JsonResponse + { + + $this->validation->make($request->all(), [ + 'name' => ['required', 'string', 'max:255'], + 'redirect_uris' => ['required', 'url'], + 'confidential' => 'boolean', + ])->validate(); + + // Creating an OAuth app client that belongs to the given user... + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name : $request->input('name'), + redirectUris: [$request->input('redirect_uris')], + confidential: $request->input('confidential'), + user : auth()->user() + ); + $arr = $client->toArray(); + $arr['plainSecret'] = $client->plainSecret; + return response()->json($arr); + } + + public function regenerateClientSecret(Request $request, string $clientId): JsonResponse | Response | Client + { + + + $client = auth()->user()->oauthApps()->where('revoked', false)->find($clientId); + if (!$client) { + return new Response('', 404); + } + //$client-> + $this->clients->regenerateSecret($client); + $arr = $client->toArray(); + $arr['plainSecret'] = $client->plainSecret; + return response()->json($arr); + + } + + public function updateClient(Request $request, string $clientId): Response | Client + { + $client = auth()->user()->oauthApps()->where('revoked', false)->find($clientId); + + if (!$client) { + return new Response('', 404); + } + + $this->validation->make($request->all(), [ + 'name' => ['required', 'string', 'max:255'], + 'redirect_uris' => ['required', 'url'], + ])->validate(); + + $this->clients->update( + $client, $request->input('name'), explode(',', $request->input('redirect_uris')) + ); // FIXME replace + + return $client; + } + + public function destroyClient(Request $request, string $clientId): Response + { + /** @var Client $client */ + $client = auth()->user()->oauthApps()->where('revoked', false)->find($clientId); + + if (!$client) { + return new Response('', 404); + } + + $client->tokens()->with('refreshToken')->each(function (Token $token): void { + $token->refreshToken?->revoke(); + $token->revoke(); + }); + + $client->forceFill(['revoked' => true])->save(); + + return new Response('', Response::HTTP_NO_CONTENT); + } + public function storePersonalAccessToken(Request $request): JsonResponse { $this->validation->make($request->all(), [ @@ -85,6 +162,7 @@ class OAuthController extends Controller public function destroyPersonalAccessToken(Request $request, string $tokenId): Response { +// auth()->user()->fin $token = $this->tokenRepository->findForUser( $tokenId, $request->user() ); diff --git a/resources/assets/v1/src/components/passport/Clients.vue b/resources/assets/v1/src/components/passport/Clients.vue index 17e8a776e5..ddb4fbf729 100644 --- a/resources/assets/v1/src/components/passport/Clients.vue +++ b/resources/assets/v1/src/components/passport/Clients.vue @@ -20,427 +20,463 @@ diff --git a/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue b/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue index 8dd9a9b1c9..781d4280ad 100644 --- a/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue +++ b/resources/assets/v1/src/components/passport/PersonalAccessTokens.vue @@ -229,7 +229,7 @@ export default { */ prepareComponent() { this.getTokens(); - this.getScopes(); + // this.getScopes(); $('#modal-create-token').on('shown.bs.modal', () => { $('#create-token-name').focus(); diff --git a/resources/assets/v1/src/components/profile/ProfileOptions.vue b/resources/assets/v1/src/components/profile/ProfileOptions.vue index 4f8ad9ada7..892faf7ea4 100644 --- a/resources/assets/v1/src/components/profile/ProfileOptions.vue +++ b/resources/assets/v1/src/components/profile/ProfileOptions.vue @@ -25,11 +25,6 @@ -
-
- -
-
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index b9f19ed373..319c014b74 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1639,6 +1639,8 @@ 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', + 'oauth_tokens_explain' => 'This installation of Firefly III can be reached at :link. This applies to you of course, but also to web applications, AI-agents and other remote programs. However, this access is only possible with the use of Personal Access Tokens or OAuth Clients. Both can be managed on this page. Beware that contrary to previous versions, Firefly III will no longer show you the client secret more than once.', + 'regenerate_secret' => 'Regenerate secret', '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.', @@ -1662,8 +1664,8 @@ return [ 'profile_oauth_client_secret_expl' => 'Here is your new client secret. This is the only time it will be shown so don\'t lose it! You may now use this secret to make API requests.', 'profile_personal_access_tokens' => 'Personal Access Tokens', 'profile_personal_access_token' => 'Personal Access Token', - 'profile_oauth_confidential' => 'Confidential', - 'profile_oauth_confidential_help' => 'Require the client to authenticate with a secret. Confidential clients can hold credentials in a secure way without exposing them to unauthorized parties. Public applications, such as native desktop or JavaScript SPA applications, are unable to hold secrets securely.', + 'profile_oauth_confidential' => 'Keep a secret?', + 'profile_oauth_confidential_help' => 'Can the application you\'re using this for keep a secret? The Firefly III Data Importer CANNOT keep a secret, so UNCHECK the box. In other cases, it\'s up to you.', 'profile_personal_access_token_explanation' => 'Here is your new personal access token. This is the only time it will be shown so don\'t lose it! You may now use this token to make API requests.', 'profile_no_personal_access_token' => 'You have not created any personal access tokens.', 'profile_create_new_token' => 'Create new token', diff --git a/resources/views/profile/index.twig b/resources/views/profile/index.twig index 087445222c..a8e8c93379 100644 --- a/resources/views/profile/index.twig +++ b/resources/views/profile/index.twig @@ -19,17 +19,6 @@
  • {{ 'command_line_token'|_ }}
  • -
  • - {{ 'oauth'|_ }} -
  • - {# - {% if true == isInternalAuth %} -
  • - {{ 'pref_two_factor_auth'|_ }} -
  • - {% endif %} - #}
  • {{ 'delete_stuff_header'|_ }} @@ -110,52 +99,6 @@
  • - -
    -
    -
    - - {# - {% if true == isInternalAuth %} - -
    -
    -
    -

    {{ 'pref_two_factor_auth_help'|_ }}

    - {% if enabled2FA == true %} -

    - {{ trans_choice('firefly.pref_two_factor_backup_code_count', mfaBackupCount) }} -

    - - -
    - - -
    -
    - - -
    - {% else %} -
    - - -
    - {% endif %} -
    -
    -
    - {% endif %} - #} -
    diff --git a/resources/views/profile/oauth/index.twig b/resources/views/profile/oauth/index.twig index 981ec0ae87..2067afd1f8 100644 --- a/resources/views/profile/oauth/index.twig +++ b/resources/views/profile/oauth/index.twig @@ -9,7 +9,21 @@
    - +
    +
    +
    +
    +

    {{ 'oauth_tokens'|_ }}

    +
    + +
    +

    + {{ trans('firefly.oauth_tokens_explain', {link: link})|raw }} +

    +
    +
    +
    +
    diff --git a/routes/web.php b/routes/web.php index 2d8d39074f..44830e5ea9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -48,6 +48,10 @@ Route::group( // clients: Route::get('/clients', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@listClients', 'as' => 'clients.index']); + Route::post('/clients', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@storeClient', 'as' => 'clients.store']); + Route::post('/clients/regenerate/{client_id}', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@regenerateClientSecret', 'as' => 'clients.regen']); + Route::put('/clients/{client_id}', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@updateClient', 'as' => 'clients.update']); + Route::delete('/clients/{client_id}', ['uses' => 'FireflyIII\Http\Controllers\Profile\OAuthController@destroyClient', 'as' => 'clients.destroy']); } ); @@ -71,10 +75,6 @@ Route::group( // Route::delete('/authorize', ['uses' => 'DenyAuthorizationController@deny', 'as' => 'authorizations.deny']); // Route::get('/tokens', ['uses' => 'AuthorizedAccessTokenController@forUser', 'as' => 'tokens.index']); // Route::delete('/tokens/{token_id}', ['uses' => 'AuthorizedAccessTokenController@destroy', 'as' => 'tokens.destroy']); -// Route::get('/clients', ['uses' => 'ClientController@forUser', 'as' => 'clients.index']); -// Route::post('/clients', ['uses' => 'ClientController@store', 'as' => 'clients.store']); -// Route::put('/clients/{client_id}', ['uses' => 'ClientController@update', 'as' => 'clients.update']); -// Route::delete('/clients/{client_id}', ['uses' => 'ClientController@destroy', 'as' => 'clients.destroy']); // Route::get('/scopes', ['uses' => 'ScopeController@all', 'as' => 'scopes.index']); // Route::get('/personal-access-tokens', ['uses' => 'PersonalAccessTokenController@forUser', 'as' => 'personal.tokens.index']); // Route::post('/personal-access-tokens', ['uses' => 'PersonalAccessTokenController@store', 'as' => 'personal.tokens.store']);