From d0e0102752c1713c938ea2cc4d0d1dcbc32cca5d Mon Sep 17 00:00:00 2001
From: Bernd Bestel
Date: Tue, 23 May 2023 20:31:51 +0200
Subject: [PATCH] API keys can now have a description
---
changelog/70_UNRELEASED_xxxx.xx.xx.md | 1 +
controllers/CalendarApiController.php | 3 +-
controllers/OpenApiController.php | 21 +++++++++--
migrations/0220.sql | 2 ++
public/viewjs/manageapikeys.js | 52 +++++++++++++++------------
public/viewjs/mealplan.js | 8 ++---
services/ApiKeyService.php | 7 ++--
views/manageapikeys.blade.php | 50 +++++++++++++++++++++++---
8 files changed, 107 insertions(+), 37 deletions(-)
create mode 100644 migrations/0220.sql
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