mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Added possibility to list all available API keys at the user settings page
This commit is contained in:
parent
040e86ea6d
commit
35a0e8464a
9 changed files with 245 additions and 42 deletions
|
@ -20,6 +20,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {Dropdown} from "bootstrap";
|
import {Dropdown} from "bootstrap";
|
||||||
|
import ClipboardJS from "clipboard";
|
||||||
|
|
||||||
class RegisterEventHelper {
|
class RegisterEventHelper {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -27,6 +28,11 @@ class RegisterEventHelper {
|
||||||
this.configureDropdowns();
|
this.configureDropdowns();
|
||||||
this.registerSpecialCharInput();
|
this.registerSpecialCharInput();
|
||||||
|
|
||||||
|
//Initialize ClipboardJS
|
||||||
|
this.registerLoadHandler(() => {
|
||||||
|
new ClipboardJS('.btn');
|
||||||
|
})
|
||||||
|
|
||||||
this.registerModalDropRemovalOnFormSubmit();
|
this.registerModalDropRemovalOnFormSubmit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -413,17 +413,20 @@ class UserSettingsController extends AbstractController
|
||||||
|
|
||||||
$form = $this->createFormBuilder($token)
|
$form = $this->createFormBuilder($token)
|
||||||
->add('name', TextType::class, [
|
->add('name', TextType::class, [
|
||||||
'label' => 'user.api_token.name',
|
'label' => 'api_tokens.name',
|
||||||
])
|
|
||||||
->add('valid_until', DateTimeType::class, [
|
|
||||||
'label' => 'user.api_token.valid_until',
|
|
||||||
'widget' => 'single_text',
|
|
||||||
'required' => false,
|
|
||||||
'html5' => true
|
|
||||||
])
|
])
|
||||||
->add('level', EnumType::class, [
|
->add('level', EnumType::class, [
|
||||||
'class' => ApiTokenLevel::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, [
|
->add('submit', SubmitType::class, [
|
||||||
'label' => 'save',
|
'label' => 'save',
|
||||||
|
|
|
@ -72,7 +72,7 @@ class ApiToken
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||||
#[Groups('token:read')]
|
#[Groups('token:read')]
|
||||||
private ?\DateTimeInterface $valid_until = null;
|
private ?\DateTimeInterface $valid_until;
|
||||||
|
|
||||||
#[ORM\Column(length: 68, unique: true)]
|
#[ORM\Column(length: 68, unique: true)]
|
||||||
private string $token;
|
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.
|
// 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));
|
$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
|
public function getTokenType(): ApiTokenType
|
||||||
|
|
|
@ -37,10 +37,14 @@ enum ApiTokenLevel: int
|
||||||
* The token can read and edit (non-sensitive) data.
|
* The token can read and edit (non-sensitive) data.
|
||||||
*/
|
*/
|
||||||
case EDIT = 2;
|
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.
|
* 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.
|
* 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],
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -315,11 +315,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||||
#[ORM\Column(type: Types::BOOLEAN)]
|
#[ORM\Column(type: Types::BOOLEAN)]
|
||||||
protected bool $saml_user = false;
|
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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->attachments = new ArrayCollection();
|
$this->attachments = new ArrayCollection();
|
||||||
|
@ -1013,7 +1008,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all API tokens of the user.
|
* Return all API tokens of the user.
|
||||||
* @return Collection
|
* @return Collection<int, ApiToken>
|
||||||
*/
|
*/
|
||||||
public function getApiTokens(): Collection
|
public function getApiTokens(): Collection
|
||||||
{
|
{
|
||||||
|
@ -1039,24 +1034,4 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||||
{
|
{
|
||||||
$this->api_tokens->removeElement($apiToken);
|
$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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
61
templates/users/_api_tokens.html.twig
Normal file
61
templates/users/_api_tokens.html.twig
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{# @var user \App\Entity\UserSystem\User #}
|
||||||
|
|
||||||
|
{% macro format_date(datetime) %}
|
||||||
|
{% if datetime is null %}
|
||||||
|
<i>{% trans %}datetime.never{% endtrans %}</i>
|
||||||
|
{% else %}
|
||||||
|
{{ datetime|format_datetime }}
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fa-solid fa-plug fa-fw" aria-hidden="true"></i>
|
||||||
|
{% trans %}user.settings.api_tokens{% endtrans %}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="text-muted">{% trans %}user.settings.api_tokens.description{% endtrans %}</span><br>
|
||||||
|
<a href="{{ path('api_doc') }}">{% trans %}user.settings.show_api_documentation{% endtrans %}</a>
|
||||||
|
|
||||||
|
{% if user.apiTokens.empty %}
|
||||||
|
<br><br>
|
||||||
|
<b>{% trans %}user.settings.api_tokens.no_api_tokens_yet{% endtrans %}</b>
|
||||||
|
{% else %}
|
||||||
|
<table class="table table-striped table-bordered table-hover table-sm mt-2">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans %}api_tokens.name{% endtrans %}</th>
|
||||||
|
<th>{% trans %}api_tokens.access_level{% endtrans %}</th>
|
||||||
|
<th>{% trans %}api_tokens.expiration_date{% endtrans %}</th>
|
||||||
|
<th>{% trans %}tfa_u2f.keys.added_date{% endtrans %}</th>
|
||||||
|
<th>{% trans %}api_tokens.last_time_used{% endtrans %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for api_token in user.apiTokens %}
|
||||||
|
{# @var api_token \App\Entity\UserSystem\ApiToken #}
|
||||||
|
<tr>
|
||||||
|
<td>{{ api_token.name }}</td>
|
||||||
|
<td>{{ api_token.level.translationKey|trans }}</td>
|
||||||
|
<td>
|
||||||
|
{{ _self.format_date(api_token.validUntil) }}
|
||||||
|
{% if api_token.valid %}
|
||||||
|
<span class="badge bg-success badge-success">{% trans %}api_token.valid{% endtrans %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-warning badge-warning">{% trans %}api_token.expired{% endtrans %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ _self.format_date(api_token.addedDate) }}</td>
|
||||||
|
<td>{{ _self.format_date(api_token.lastTimeUsed) }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a href="{{ path('user_api_token_create') }}" class="btn btn-success" ><i class="fas fa-plus-square fa-fw"></i> {% trans %}api_token.create_new{% endtrans %}</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,16 +1,30 @@
|
||||||
{% extends "main_card.html.twig" %}
|
{% extends "main_card.html.twig" %}
|
||||||
|
|
||||||
{% block card_title %}Add API token{% endblock %}
|
{% block title %}{% trans %}api_token.create_new{% endtrans %}{% endblock %}
|
||||||
|
|
||||||
|
{% block card_title %}
|
||||||
|
<i class="fa-solid fa-plug fa-fw" aria-hidden="true"></i>
|
||||||
|
{% trans %}api_token.create_new{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block card_content %}
|
{% block card_content %}
|
||||||
{# Show API secret after submit #}
|
{# Show API secret after submit #}
|
||||||
|
|
||||||
{% if secret is not null %}
|
{% if secret is not null %}
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
Your API token is: <strong>{{ secret }}</strong><br>
|
<b>{% trans %}api_tokens.your_token_is{% endtrans %}:</b><br>
|
||||||
<span class="text-muted">Please save it. You wont be able to see it again!</span>
|
<div class="input-group input-group-sm">
|
||||||
</div>
|
<input type="text" class="form-control" value="{{ secret }}" readonly>
|
||||||
{% endif %}
|
<button class="btn btn-outline-secondary" type="button" data-clipboard-text="{{ secret }}">
|
||||||
|
<i class="fa-solid fa-clipboard fa-fw" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<i>{% trans %}api_tokens.please_save_it{% endtrans %}</i>
|
||||||
|
<br>
|
||||||
|
|
||||||
{{ form(form) }}
|
<a href="{{ path('user_settings') }}" class="btn btn-primary mt-4">{% trans %}api_tokens.create_new.back_to_user_settings{% endtrans %}</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ form(form) }}
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -76,4 +76,6 @@
|
||||||
{{ form_end(pw_form) }}
|
{{ form_end(pw_form) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% include "users/_api_tokens.html.twig" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -11603,5 +11603,131 @@ Please note, that you can not impersonate a disabled user. If you try you will g
|
||||||
<target>Show available Part-DB updates</target>
|
<target>Show available Part-DB updates</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="bItnXAm" name="user.settings.api_tokens">
|
||||||
|
<segment>
|
||||||
|
<source>user.settings.api_tokens</source>
|
||||||
|
<target>API tokens</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="stkMDET" name="user.settings.api_tokens.description">
|
||||||
|
<segment>
|
||||||
|
<source>user.settings.api_tokens.description</source>
|
||||||
|
<target>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.</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="pd794fq" name="api_tokens.name">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.name</source>
|
||||||
|
<target>Name</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="XodVxQi" name="api_tokens.access_level">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.access_level</source>
|
||||||
|
<target>Access level</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="kHaDSpK" name="api_tokens.expiration_date">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.expiration_date</source>
|
||||||
|
<target>Expiration date</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="b5uLOjr" name="api_tokens.added_date">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.added_date</source>
|
||||||
|
<target>api_tokens.added_date</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="QPwuYKl" name="api_tokens.last_time_used">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.last_time_used</source>
|
||||||
|
<target>Last time used</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="G91glEH" name="datetime.never">
|
||||||
|
<segment>
|
||||||
|
<source>datetime.never</source>
|
||||||
|
<target>Never</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="xPeKpJU" name="api_token.valid">
|
||||||
|
<segment>
|
||||||
|
<source>api_token.valid</source>
|
||||||
|
<target>Valid</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="jlBlmje" name="api_token.expired">
|
||||||
|
<segment>
|
||||||
|
<source>api_token.expired</source>
|
||||||
|
<target>Expired</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="Kmdx4SB" name="user.settings.show_api_documentation">
|
||||||
|
<segment>
|
||||||
|
<source>user.settings.show_api_documentation</source>
|
||||||
|
<target>Show API documentation</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="hAUCZVo" name="api_token.create_new">
|
||||||
|
<segment>
|
||||||
|
<source>api_token.create_new</source>
|
||||||
|
<target>Create new API token</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="eX17pK8" name="api_token.level.read_only">
|
||||||
|
<segment>
|
||||||
|
<source>api_token.level.read_only</source>
|
||||||
|
<target>Read-Only</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="PMVjK5p" name="api_token.level.edit">
|
||||||
|
<segment>
|
||||||
|
<source>api_token.level.edit</source>
|
||||||
|
<target>Edit</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="1dVnGpy" name="api_token.level.admin">
|
||||||
|
<segment>
|
||||||
|
<source>api_token.level.admin</source>
|
||||||
|
<target>Admin</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="gY4wVwS" name="api_token.level.full">
|
||||||
|
<segment>
|
||||||
|
<source>api_token.level.full</source>
|
||||||
|
<target>Full</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="OAFErYa" name="api_tokens.access_level.help">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.access_level.help</source>
|
||||||
|
<target>You can restrict, what the API token can access. The access is always limited by the permissions of your user.</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="Xjs_v91" name="api_tokens.expiration_date.help">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.expiration_date.help</source>
|
||||||
|
<target>After this date, the token is not usable anymore. Leave empty if the token should never expire.</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="0e7mD8q" name="api_tokens.your_token_is">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.your_token_is</source>
|
||||||
|
<target>Your API token is</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="nre_kP_" name="api_tokens.please_save_it">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.please_save_it</source>
|
||||||
|
<target>Please save it. You will not be able to see it again!</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="b17FeDX" name="api_tokens.create_new.back_to_user_settings">
|
||||||
|
<segment>
|
||||||
|
<source>api_tokens.create_new.back_to_user_settings</source>
|
||||||
|
<target>Back to user settings</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue