diff --git a/assets/js/register_events.js b/assets/js/register_events.js index 22e91fdf..5d2aece0 100644 --- a/assets/js/register_events.js +++ b/assets/js/register_events.js @@ -20,6 +20,7 @@ 'use strict'; import {Dropdown} from "bootstrap"; +import ClipboardJS from "clipboard"; class RegisterEventHelper { constructor() { @@ -27,6 +28,11 @@ class RegisterEventHelper { this.configureDropdowns(); this.registerSpecialCharInput(); + //Initialize ClipboardJS + this.registerLoadHandler(() => { + new ClipboardJS('.btn'); + }) + this.registerModalDropRemovalOnFormSubmit(); } diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index acd18206..3f9d89bd 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -413,17 +413,20 @@ class UserSettingsController extends AbstractController $form = $this->createFormBuilder($token) ->add('name', TextType::class, [ - 'label' => 'user.api_token.name', - ]) - ->add('valid_until', DateTimeType::class, [ - 'label' => 'user.api_token.valid_until', - 'widget' => 'single_text', - 'required' => false, - 'html5' => true + 'label' => 'api_tokens.name', ]) ->add('level', EnumType::class, [ 'class' => ApiTokenLevel::class, - 'label' => 'user.api_token.level', + 'label' => 'api_tokens.access_level', + 'help' => 'api_tokens.access_level.help', + 'choice_label' => fn (ApiTokenLevel $level) => $level->getTranslationKey(), + ]) + ->add('valid_until', DateTimeType::class, [ + 'label' => 'api_tokens.expiration_date', + 'widget' => 'single_text', + 'help' => 'api_tokens.expiration_date.help', + 'required' => false, + 'html5' => true ]) ->add('submit', SubmitType::class, [ 'label' => 'save', diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index 03b87a7a..e344e464 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -72,7 +72,7 @@ class ApiToken #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Groups('token:read')] - private ?\DateTimeInterface $valid_until = null; + private ?\DateTimeInterface $valid_until; #[ORM\Column(length: 68, unique: true)] private string $token; @@ -89,6 +89,9 @@ class ApiToken { // Generate a rondom token on creation. The tokenType is 3 characters long (plus underscore), so the token is 68 characters long. $this->token = $tokenType->getTokenPrefix() . bin2hex(random_bytes(32)); + + //By default, tokens are valid for 1 year. + $this->valid_until = new \DateTime('+1 year'); } public function getTokenType(): ApiTokenType diff --git a/src/Entity/UserSystem/ApiTokenLevel.php b/src/Entity/UserSystem/ApiTokenLevel.php index ee2f6bb5..8289e57c 100644 --- a/src/Entity/UserSystem/ApiTokenLevel.php +++ b/src/Entity/UserSystem/ApiTokenLevel.php @@ -37,10 +37,14 @@ enum ApiTokenLevel: int * The token can read and edit (non-sensitive) data. */ case EDIT = 2; + /** + * The token can do some administrative tasks (like viewing all log entries), but can not change passwords and create new tokens. + */ + case ADMIN = 3; /** * The token can do everything the user can do. */ - case FULL = 3; + case FULL = 4; /** * Returns the additional roles that the authenticated user should have when using this token. @@ -55,4 +59,13 @@ enum ApiTokenLevel: int self::FULL => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_FULL], }; } + + /** + * Returns the translation key for the name of this token level. + * @return string + */ + public function getTranslationKey(): string + { + return 'api_token.level.' . strtolower($this->name); + } } \ No newline at end of file diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index e2a3ff8b..ae4f90d0 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -315,11 +315,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[ORM\Column(type: Types::BOOLEAN)] protected bool $saml_user = false; - /** - * @var ApiToken|null The api token which is used to authenticate the user, or null if the user is not authenticated via api token - */ - private ?ApiToken $authenticating_api_token = null; - public function __construct() { $this->attachments = new ArrayCollection(); @@ -1013,7 +1008,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Return all API tokens of the user. - * @return Collection + * @return Collection */ public function getApiTokens(): Collection { @@ -1039,24 +1034,4 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe { $this->api_tokens->removeElement($apiToken); } - - /** - * Mark the user as authenticated with an API token, should only be used by the API token authenticator. - * @param ApiToken $apiToken - * @return void - */ - public function markAsApiTokenAuthenticated(ApiToken $apiToken): void - { - $this->authenticating_api_token = $apiToken; - } - - /** - * Return the API token that is currently authenticating the user or null if the user is not authenticated with an API token. - * @return ApiToken|null - */ - public function getAuthenticatingApiToken(): ?ApiToken - { - return $this->authenticating_api_token; - } - } diff --git a/templates/users/_api_tokens.html.twig b/templates/users/_api_tokens.html.twig new file mode 100644 index 00000000..d52b8970 --- /dev/null +++ b/templates/users/_api_tokens.html.twig @@ -0,0 +1,61 @@ +{# @var user \App\Entity\UserSystem\User #} + +{% macro format_date(datetime) %} + {% if datetime is null %} + {% trans %}datetime.never{% endtrans %} + {% else %} + {{ datetime|format_datetime }} + {% endif %} +{% endmacro %} + +
+
+ + {% trans %}user.settings.api_tokens{% endtrans %} +
+
+ {% trans %}user.settings.api_tokens.description{% endtrans %}
+ {% trans %}user.settings.show_api_documentation{% endtrans %} + + {% if user.apiTokens.empty %} +

+ {% trans %}user.settings.api_tokens.no_api_tokens_yet{% endtrans %} + {% else %} + + + + + + + + + + + + + + {% for api_token in user.apiTokens %} + {# @var api_token \App\Entity\UserSystem\ApiToken #} + + + + + + + + {% endfor %} + +
{% trans %}api_tokens.name{% endtrans %}{% trans %}api_tokens.access_level{% endtrans %}{% trans %}api_tokens.expiration_date{% endtrans %}{% trans %}tfa_u2f.keys.added_date{% endtrans %}{% trans %}api_tokens.last_time_used{% endtrans %}
{{ api_token.name }}{{ api_token.level.translationKey|trans }} + {{ _self.format_date(api_token.validUntil) }} + {% if api_token.valid %} + {% trans %}api_token.valid{% endtrans %} + {% else %} + {% trans %}api_token.expired{% endtrans %} + {% endif %} + {{ _self.format_date(api_token.addedDate) }}{{ _self.format_date(api_token.lastTimeUsed) }}
+ {% endif %} + + {% trans %}api_token.create_new{% endtrans %} + +
+
\ No newline at end of file diff --git a/templates/users/api_token_create.html.twig b/templates/users/api_token_create.html.twig index 57b86708..1a159e73 100644 --- a/templates/users/api_token_create.html.twig +++ b/templates/users/api_token_create.html.twig @@ -1,16 +1,30 @@ {% extends "main_card.html.twig" %} -{% block card_title %}Add API token{% endblock %} +{% block title %}{% trans %}api_token.create_new{% endtrans %}{% endblock %} + +{% block card_title %} + + {% trans %}api_token.create_new{% endtrans %} +{% endblock %} {% block card_content %} {# Show API secret after submit #} {% if secret is not null %}
- Your API token is: {{ secret }}
- Please save it. You wont be able to see it again! -
- {% endif %} + {% trans %}api_tokens.your_token_is{% endtrans %}:
+
+ + +
+ {% trans %}api_tokens.please_save_it{% endtrans %} +
- {{ form(form) }} + {% trans %}api_tokens.create_new.back_to_user_settings{% endtrans %} + + {% else %} + {{ form(form) }} + {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/users/user_settings.html.twig b/templates/users/user_settings.html.twig index 650ebe97..359b993e 100644 --- a/templates/users/user_settings.html.twig +++ b/templates/users/user_settings.html.twig @@ -76,4 +76,6 @@ {{ form_end(pw_form) }} + + {% include "users/_api_tokens.html.twig" %} {% endblock %} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 92e04ef6..7771601e 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -11603,5 +11603,131 @@ Please note, that you can not impersonate a disabled user. If you try you will g Show available Part-DB updates + + + user.settings.api_tokens + API tokens + + + + + user.settings.api_tokens.description + Using an API token, other applications can access Part-DB with your user rights, to perform various actions using the Part-DB REST API. If you delete an API token here, the application, which uses the token, will no longer be able to access Part-DB on your behalf. + + + + + api_tokens.name + Name + + + + + api_tokens.access_level + Access level + + + + + api_tokens.expiration_date + Expiration date + + + + + api_tokens.added_date + api_tokens.added_date + + + + + api_tokens.last_time_used + Last time used + + + + + datetime.never + Never + + + + + api_token.valid + Valid + + + + + api_token.expired + Expired + + + + + user.settings.show_api_documentation + Show API documentation + + + + + api_token.create_new + Create new API token + + + + + api_token.level.read_only + Read-Only + + + + + api_token.level.edit + Edit + + + + + api_token.level.admin + Admin + + + + + api_token.level.full + Full + + + + + api_tokens.access_level.help + You can restrict, what the API token can access. The access is always limited by the permissions of your user. + + + + + api_tokens.expiration_date.help + After this date, the token is not usable anymore. Leave empty if the token should never expire. + + + + + api_tokens.your_token_is + Your API token is + + + + + api_tokens.please_save_it + Please save it. You will not be able to see it again! + + + + + api_tokens.create_new.back_to_user_settings + Back to user settings + +