mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
API keys can now have a description
This commit is contained in:
parent
bc5051351a
commit
d0e0102752
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
2
migrations/0220.sql
Normal file
2
migrations/0220.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE api_keys
|
||||
ADD description TEXT;
|
@ -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: "<p class='text-center'>" + qrcodeHtml + "</p>",
|
||||
message: "<h1>" + __t("API key") + "</h1><h2 class='text-muted'>" + apiKeyDescription + "</h2><p><hr>" + QrCodeImgHtml(content) + "</p>",
|
||||
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()));
|
||||
});
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -4,6 +4,14 @@
|
||||
|
||||
@section('title', $__t('API keys'))
|
||||
|
||||
@push('pageStyles')
|
||||
<style>
|
||||
.modal-body {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@ -25,8 +33,9 @@
|
||||
</div>
|
||||
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
||||
id="related-links">
|
||||
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
||||
href="{{ $U('/manageapikeys/new') }}">
|
||||
<a id="add-api-key-button"
|
||||
class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
||||
href="#">
|
||||
{{ $__t('Add') }}
|
||||
</a>
|
||||
</div>
|
||||
@ -74,6 +83,7 @@
|
||||
data-table-selector="#apikeys-table"
|
||||
href="#"><i class="fa-solid fa-eye"></i></a>
|
||||
</th>
|
||||
<th>{{ $__t('Description') }}</th>
|
||||
<th>{{ $__t('API key') }}</th>
|
||||
<th class="allow-grouping">{{ $__t('User') }}</th>
|
||||
<th>{{ $__t('Expires') }}</th>
|
||||
@ -84,12 +94,12 @@
|
||||
</thead>
|
||||
<tbody class="d-none">
|
||||
@foreach($apiKeys as $apiKey)
|
||||
<tr id="apiKeyRow_{{ $apiKey->id }}">
|
||||
<tr class="@if($apiKey->id == $selectedKeyId) table-primary @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-danger btn-sm apikey-delete-button"
|
||||
href="#"
|
||||
data-apikey-id="{{ $apiKey->id }}"
|
||||
data-apikey-apikey="{{ $apiKey->api_key }}"
|
||||
data-apikey-key="{{ $apiKey->api_key }}"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Delete this item') }}">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
@ -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') }}">
|
||||
<i class="fa-solid fa-qrcode"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ $apiKey->description }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $apiKey->api_key }}
|
||||
</td>
|
||||
@ -133,4 +147,32 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade"
|
||||
id="add-api-key-modal"
|
||||
tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title w-100">{{ $__t('Create new API key') }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ $__t('Name') }}</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="description"
|
||||
name="description">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button"
|
||||
class="btn btn-secondary"
|
||||
data-dismiss="modal">{{ $__t('Cancel') }}</button>
|
||||
<button id="new-api-key-button"
|
||||
class="btn btn-primary">{{ $__t('OK') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
||||
|
Loading…
x
Reference in New Issue
Block a user