diff --git a/changelog/70_UNRELEASED_xxxx.xx.xx.md b/changelog/70_UNRELEASED_xxxx.xx.xx.md index 120703a6..c1c91547 100644 --- a/changelog/70_UNRELEASED_xxxx.xx.xx.md +++ b/changelog/70_UNRELEASED_xxxx.xx.xx.md @@ -115,3 +115,4 @@ - The following entities are now also available via the endpoint `/objects/{entity}` (only listing, no edit) - `quantity_unit_conversions_resolved` (returns all final/resolved conversion factors per product and any directly or indirectly related quantity units) - The endpoint `/batteries` now also returns the corresponding battery object (as field/property `battery`) +- API keys can now have a description (to e.g. track where the corresponding key is used) diff --git a/controllers/CalendarApiController.php b/controllers/CalendarApiController.php index d1d57806..e7419d21 100644 --- a/controllers/CalendarApiController.php +++ b/controllers/CalendarApiController.php @@ -2,6 +2,7 @@ namespace Grocy\Controllers; +use Grocy\Services\ApiKeyService; use Eluceo\iCal\Domain\Entity\Calendar; use Eluceo\iCal\Domain\Entity\Event; use Eluceo\iCal\Domain\Entity\TimeZone; @@ -94,7 +95,7 @@ class CalendarApiController extends BaseApiController try { return $this->ApiResponse($response, [ - 'url' => $this->AppContainer->get('UrlManager')->ConstructUrl('/api/calendar/ical?secret=' . $this->getApiKeyService()->GetOrCreateApiKey(\Grocy\Services\ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL)) + 'url' => $this->AppContainer->get('UrlManager')->ConstructUrl('/api/calendar/ical?secret=' . $this->getApiKeyService()->GetOrCreateApiKey(ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL)) ]); } catch (\Exception $ex) diff --git a/controllers/OpenApiController.php b/controllers/OpenApiController.php index d7b18f07..9602e30e 100644 --- a/controllers/OpenApiController.php +++ b/controllers/OpenApiController.php @@ -3,6 +3,7 @@ namespace Grocy\Controllers; use Grocy\Controllers\Users\User; +use Grocy\Services\ApiKeyService; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -10,22 +11,36 @@ class OpenApiController extends BaseApiController { public function ApiKeysList(Request $request, Response $response, array $args) { + $selectedKeyId = -1; + if (isset($request->getQueryParams()['key']) && filter_var($request->getQueryParams()['key'], FILTER_VALIDATE_INT)) + { + $selectedKeyId = $request->getQueryParams()['key']; + } + $apiKeys = $this->getDatabase()->api_keys(); if (!User::hasPermissions(User::PERMISSION_ADMIN)) { $apiKeys = $apiKeys->where('user_id', GROCY_USER_ID); } + return $this->renderPage($response, 'manageapikeys', [ 'apiKeys' => $apiKeys, - 'users' => $this->getDatabase()->users() + 'users' => $this->getDatabase()->users(), + 'selectedKeyId' => $selectedKeyId ]); } public function CreateNewApiKey(Request $request, Response $response, array $args) { - $newApiKey = $this->getApiKeyService()->CreateApiKey(); + $description = null; + if (isset($request->getQueryParams()['description'])) + { + $description = $request->getQueryParams()['description']; + } + + $newApiKey = $this->getApiKeyService()->CreateApiKey(ApiKeyService::API_KEY_TYPE_DEFAULT, $description); $newApiKeyId = $this->getApiKeyService()->GetApiKeyId($newApiKey); - return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl("/manageapikeys?CreatedApiKeyId=$newApiKeyId")); + return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl("/manageapikeys?key=$newApiKeyId")); } public function DocumentationSpec(Request $request, Response $response, array $args) diff --git a/migrations/0220.sql b/migrations/0220.sql new file mode 100644 index 00000000..52617b9c --- /dev/null +++ b/migrations/0220.sql @@ -0,0 +1,2 @@ +ALTER TABLE api_keys +ADD description TEXT; diff --git a/public/viewjs/manageapikeys.js b/public/viewjs/manageapikeys.js index 4323f0db..109b4527 100644 --- a/public/viewjs/manageapikeys.js +++ b/public/viewjs/manageapikeys.js @@ -1,5 +1,5 @@ var apiKeysTable = $('#apikeys-table').DataTable({ - 'order': [[4, 'desc']], + 'order': [[6, 'desc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, { 'searchable': false, "targets": 0 } @@ -8,12 +8,6 @@ $('#apikeys-table tbody').removeClass("d-none"); apiKeysTable.columns.adjust().draw(); -var createdApiKeyId = GetUriParam('CreatedApiKeyId'); -if (createdApiKeyId !== undefined) -{ - animateCSS("#apiKeyRow_" + createdApiKeyId, "pulse"); -} - $("#search").on("keyup", Delay(function() { var value = $(this).val(); @@ -33,8 +27,9 @@ $("#clear-filter-button").on("click", function() $(document).on('click', '.apikey-delete-button', function(e) { - var objectName = $(e.currentTarget).attr('data-apikey-apikey'); - var objectId = $(e.currentTarget).attr('data-apikey-id'); + var button = $(e.currentTarget); + var objectName = button.attr('data-apikey-key'); + var objectId = button.attr('data-apikey-id'); bootbox.confirm({ message: __t('Are you sure to delete API key "%s"?', objectName), @@ -68,23 +63,36 @@ $(document).on('click', '.apikey-delete-button', function(e) }); }); -function QrCodeForApiKey(apiKeyType, apiKey) +$(".apikey-show-qr-button").on("click", function() { - var content = U('/api') + '|' + apiKey; - if (apiKeyType === 'special-purpose-calendar-ical') + var button = $(this); + var apiKey = button.data("apikey-key"); + var apiKeyType = button.data("apikey-type"); + var apiKeyDescription = button.data("apikey-description"); + + var content = U("/api") + "|" + apiKey; + if (apiKeyType === "special-purpose-calendar-ical") { - content = U('/api/calendar/ical?secret=' + apiKey); + content = U("/api/calendar/ical?secret=" + apiKey); } - return QrCodeImgHtml(content); -} - -$('.apikey-show-qr-button').on('click', function() -{ - var qrcodeHtml = QrCodeForApiKey($(this).data('apikey-type'), $(this).data('apikey-key')); bootbox.alert({ - title: __t('API key'), - message: "

" + qrcodeHtml + "

", + message: "

" + __t("API key") + "

" + apiKeyDescription + "


" + QrCodeImgHtml(content) + "

", closeButton: false }); -}) +}); + +$("#add-api-key-button").on("click", function(e) +{ + $("#add-api-key-modal").modal("show"); +}); + +$("#add-api-key-modal").on("shown.bs.modal", function(e) +{ + $("#description").focus(); +}); + +$("#new-api-key-button").on("click", function(e) +{ + window.location.href = U("/manageapikeys/new?description=" + encodeURIComponent($("#description").val())); +}); diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js index 05a3dbc8..9c05c3aa 100644 --- a/public/viewjs/mealplan.js +++ b/public/viewjs/mealplan.js @@ -478,12 +478,12 @@ $("#add-recipe-modal").on("shown.bs.modal", function(e) } Grocy.Components.RecipePicker.GetInputElement().focus(); -}) +}); $("#add-note-modal").on("shown.bs.modal", function(e) { $("#note").focus(); -}) +}); $("#add-product-modal").on("shown.bs.modal", function(e) { @@ -493,12 +493,12 @@ $("#add-product-modal").on("shown.bs.modal", function(e) } Grocy.Components.ProductPicker.GetInputElement().focus(); -}) +}); $("#copy-day-modal").on("shown.bs.modal", function(e) { Grocy.Components.DateTimePicker2.GetInputElement().focus(); -}) +}); $(document).on("click", ".remove-recipe-button, .remove-note-button, .remove-product-button", function(e) { diff --git a/services/ApiKeyService.php b/services/ApiKeyService.php index 10676af2..e1214d1c 100644 --- a/services/ApiKeyService.php +++ b/services/ApiKeyService.php @@ -8,15 +8,16 @@ class ApiKeyService extends BaseService const API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL = 'special-purpose-calendar-ical'; - public function CreateApiKey($keyType = self::API_KEY_TYPE_DEFAULT) + public function CreateApiKey(string $keyType = self::API_KEY_TYPE_DEFAULT, string $description = null) { $newApiKey = $this->GenerateApiKey(); $apiKeyRow = $this->getDatabase()->api_keys()->createRow([ 'api_key' => $newApiKey, 'user_id' => GROCY_USER_ID, - 'expires' => '2999-12-31 23:59:59', // Default is that API keys expire never - 'key_type' => $keyType + 'expires' => '2999-12-31 23:59:59', // Default is that API keys never expire + 'key_type' => $keyType, + 'description' => $description ]); $apiKeyRow->save(); diff --git a/views/manageapikeys.blade.php b/views/manageapikeys.blade.php index 9e342862..25ff2020 100644 --- a/views/manageapikeys.blade.php +++ b/views/manageapikeys.blade.php @@ -4,6 +4,14 @@ @section('title', $__t('API keys')) +@push('pageStyles') + +@endpush + @section('content')
@@ -25,8 +33,9 @@
@@ -74,6 +83,7 @@ data-table-selector="#apikeys-table" href="#"> + {{ $__t('Description') }} {{ $__t('API key') }} {{ $__t('User') }} {{ $__t('Expires') }} @@ -84,12 +94,12 @@ @foreach($apiKeys as $apiKey) - + @@ -98,11 +108,15 @@ href="#" data-apikey-key="{{ $apiKey->api_key }}" data-apikey-type="{{ $apiKey->key_type }}" + data-apikey-description="{{ $apiKey->description }}" data-toggle="tooltip" title="{{ $__t('Show a QR-Code for this API key') }}"> + + {{ $apiKey->description }} + {{ $apiKey->api_key }} @@ -133,4 +147,32 @@
+ + @stop