web: migrate Token List to web
This commit is contained in:
parent
fd28f37c0d
commit
6597d5bd28
|
@ -1,102 +0,0 @@
|
||||||
{% extends "administration/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load authentik_utils %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="pf-c-page__main-section pf-m-light">
|
|
||||||
<div class="pf-c-content">
|
|
||||||
<h1>
|
|
||||||
<i class="pf-icon pf-icon-security"></i>
|
|
||||||
{% trans 'Tokens' %}
|
|
||||||
</h1>
|
|
||||||
<p>{% trans "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." %}</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
|
||||||
<div class="pf-c-card">
|
|
||||||
{% if object_list %}
|
|
||||||
<div class="pf-c-toolbar">
|
|
||||||
<div class="pf-c-toolbar__content">
|
|
||||||
{% include 'partials/toolbar_search.html' %}
|
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
|
|
||||||
<thead>
|
|
||||||
<tr role="row">
|
|
||||||
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
|
|
||||||
<th role="columnheader" scope="col">{% trans 'User' %}</th>
|
|
||||||
<th role="columnheader" scope="col">{% trans 'Expires?' %}</th>
|
|
||||||
<th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th>
|
|
||||||
<th role="cell"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody role="rowgroup">
|
|
||||||
{% for token in object_list %}
|
|
||||||
<tr role="row">
|
|
||||||
<th role="columnheader">
|
|
||||||
<div>{{ token.identifier }}</div>
|
|
||||||
</th>
|
|
||||||
<td role="cell">
|
|
||||||
<span>
|
|
||||||
{{ token.user }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td role="cell">
|
|
||||||
<span>
|
|
||||||
{{ token.expiring|yesno:"Yes,No" }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td role="cell">
|
|
||||||
<span>
|
|
||||||
{% if not token.expiring %}
|
|
||||||
-
|
|
||||||
{% else %}
|
|
||||||
{{ token.expires }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ak-modal-button href="{% url 'authentik_admin:token-delete' pk=token.pk %}">
|
|
||||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
|
||||||
{% trans 'Delete' %}
|
|
||||||
</ak-spinner-button>
|
|
||||||
<div slot="modal"></div>
|
|
||||||
</ak-modal-button>
|
|
||||||
<ak-token-copy-button identifier="{{ token.identifier }}">
|
|
||||||
{% trans 'Copy token' %}
|
|
||||||
</ak-token-copy-button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="pf-c-pagination pf-m-bottom">
|
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="pf-c-toolbar">
|
|
||||||
<div class="pf-c-toolbar__content">
|
|
||||||
{% include 'partials/toolbar_search.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-empty-state">
|
|
||||||
<div class="pf-c-empty-state__content">
|
|
||||||
<i class="fas fa-key pf-c-empty-state__icon" aria-hidden="true"></i>
|
|
||||||
<h1 class="pf-c-title pf-m-lg">
|
|
||||||
{% trans 'No Tokens.' %}
|
|
||||||
</h1>
|
|
||||||
<div class="pf-c-empty-state__body">
|
|
||||||
{% if request.GET.search != "" %}
|
|
||||||
{% trans "Your search query doesn't match any token." %}
|
|
||||||
{% else %}
|
|
||||||
{% trans 'Currently no tokens exist.' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
|
@ -53,7 +53,6 @@ urlpatterns = [
|
||||||
name="application-delete",
|
name="application-delete",
|
||||||
),
|
),
|
||||||
# Tokens
|
# Tokens
|
||||||
path("tokens/", tokens.TokenListView.as_view(), name="tokens"),
|
|
||||||
path(
|
path(
|
||||||
"tokens/<uuid:pk>/delete/",
|
"tokens/<uuid:pk>/delete/",
|
||||||
tokens.TokenDeleteView.as_view(),
|
tokens.TokenDeleteView.as_view(),
|
||||||
|
|
|
@ -1,39 +1,12 @@
|
||||||
"""authentik Token administration"""
|
"""authentik Token administration"""
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import ListView
|
from guardian.mixins import PermissionRequiredMixin
|
||||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
|
||||||
|
|
||||||
from authentik.admin.views.utils import (
|
from authentik.admin.views.utils import DeleteMessageView
|
||||||
DeleteMessageView,
|
|
||||||
SearchListMixin,
|
|
||||||
UserPaginateListMixin,
|
|
||||||
)
|
|
||||||
from authentik.core.models import Token
|
from authentik.core.models import Token
|
||||||
|
|
||||||
|
|
||||||
class TokenListView(
|
|
||||||
LoginRequiredMixin,
|
|
||||||
PermissionListMixin,
|
|
||||||
UserPaginateListMixin,
|
|
||||||
SearchListMixin,
|
|
||||||
ListView,
|
|
||||||
):
|
|
||||||
"""Show list of all tokens"""
|
|
||||||
|
|
||||||
model = Token
|
|
||||||
permission_required = "authentik_core.view_token"
|
|
||||||
ordering = "expires"
|
|
||||||
template_name = "administration/token/list.html"
|
|
||||||
search_fields = [
|
|
||||||
"identifier",
|
|
||||||
"intent",
|
|
||||||
"user__username",
|
|
||||||
"description",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
|
class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
|
||||||
"""Delete token"""
|
"""Delete token"""
|
||||||
|
|
||||||
|
@ -41,5 +14,5 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
|
||||||
permission_required = "authentik_core.delete_token"
|
permission_required = "authentik_core.delete_token"
|
||||||
|
|
||||||
template_name = "generic/delete.html"
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy("authentik_admin:tokens")
|
success_url = "/"
|
||||||
success_message = _("Successfully deleted Token")
|
success_message = _("Successfully deleted Token")
|
||||||
|
|
|
@ -7,19 +7,14 @@ from django.contrib.auth.mixins import (
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.http.response import HttpResponseRedirect
|
from django.http.response import HttpResponseRedirect
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect, reverse
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import DetailView, UpdateView
|
from django.views.generic import DetailView, UpdateView
|
||||||
from guardian.mixins import (
|
from guardian.mixins import PermissionRequiredMixin
|
||||||
PermissionRequiredMixin,
|
|
||||||
)
|
|
||||||
|
|
||||||
from authentik.admin.forms.users import UserForm
|
from authentik.admin.forms.users import UserForm
|
||||||
from authentik.admin.views.utils import (
|
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
|
||||||
BackSuccessUrlMixin,
|
|
||||||
DeleteMessageView,
|
|
||||||
)
|
|
||||||
from authentik.core.models import Token, User
|
from authentik.core.models import Token, User
|
||||||
from authentik.lib.views import CreateAssignPermView
|
from authentik.lib.views import CreateAssignPermView
|
||||||
|
|
||||||
|
|
|
@ -19,3 +19,5 @@ class GroupViewSet(ModelViewSet):
|
||||||
|
|
||||||
queryset = Group.objects.all()
|
queryset = Group.objects.all()
|
||||||
serializer_class = GroupSerializer
|
serializer_class = GroupSerializer
|
||||||
|
search_fields = ["name", "is_superuser"]
|
||||||
|
filterset_fields = ["name", "is_superuser"]
|
||||||
|
|
|
@ -9,6 +9,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ModelSerializer, Serializer
|
from rest_framework.serializers import ModelSerializer, Serializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from authentik.core.api.users import UserSerializer
|
||||||
from authentik.core.models import Token
|
from authentik.core.models import Token
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
|
||||||
|
@ -16,10 +17,21 @@ from authentik.events.models import Event, EventAction
|
||||||
class TokenSerializer(ModelSerializer):
|
class TokenSerializer(ModelSerializer):
|
||||||
"""Token Serializer"""
|
"""Token Serializer"""
|
||||||
|
|
||||||
|
user = UserSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Token
|
model = Token
|
||||||
fields = ["pk", "identifier", "intent", "user", "description"]
|
fields = [
|
||||||
|
"pk",
|
||||||
|
"identifier",
|
||||||
|
"intent",
|
||||||
|
"user",
|
||||||
|
"description",
|
||||||
|
"expires",
|
||||||
|
"expiring",
|
||||||
|
]
|
||||||
|
depth = 2
|
||||||
|
|
||||||
|
|
||||||
class TokenViewSerializer(Serializer):
|
class TokenViewSerializer(Serializer):
|
||||||
|
@ -40,6 +52,19 @@ class TokenViewSet(ModelViewSet):
|
||||||
lookup_field = "identifier"
|
lookup_field = "identifier"
|
||||||
queryset = Token.filter_not_expired()
|
queryset = Token.filter_not_expired()
|
||||||
serializer_class = TokenSerializer
|
serializer_class = TokenSerializer
|
||||||
|
search_fields = [
|
||||||
|
"identifier",
|
||||||
|
"intent",
|
||||||
|
"user__username",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
filterset_fields = [
|
||||||
|
"identifier",
|
||||||
|
"intent",
|
||||||
|
"user__username",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
ordering = ["expires"]
|
||||||
|
|
||||||
@swagger_auto_schema(responses={200: TokenViewSerializer(many=False)})
|
@swagger_auto_schema(responses={200: TokenViewSerializer(many=False)})
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
|
|
|
@ -37,6 +37,7 @@ class UserSerializer(ModelSerializer):
|
||||||
"is_superuser",
|
"is_superuser",
|
||||||
"email",
|
"email",
|
||||||
"avatar",
|
"avatar",
|
||||||
|
"attributes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,6 +46,8 @@ class UserViewSet(ModelViewSet):
|
||||||
|
|
||||||
queryset = User.objects.none()
|
queryset = User.objects.none()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
search_fields = ["username", "name", "is_active"]
|
||||||
|
filterset_fields = ["username", "name", "is_active"]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return User.objects.all().exclude(pk=get_anonymous_user().pk)
|
return User.objects.all().exclude(pk=get_anonymous_user().pk)
|
||||||
|
|
|
@ -186,6 +186,8 @@ class StageViewSet(ReadOnlyModelViewSet):
|
||||||
|
|
||||||
queryset = Stage.objects.all()
|
queryset = Stage.objects.all()
|
||||||
serializer_class = StageSerializer
|
serializer_class = StageSerializer
|
||||||
|
search_fields = ["name"]
|
||||||
|
filterset_fields = ["name"]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Stage.objects.select_subclasses()
|
return Stage.objects.select_subclasses()
|
||||||
|
|
|
@ -61,6 +61,8 @@ class ServiceConnectionViewSet(ModelViewSet):
|
||||||
|
|
||||||
queryset = OutpostServiceConnection.objects.select_subclasses()
|
queryset = OutpostServiceConnection.objects.select_subclasses()
|
||||||
serializer_class = ServiceConnectionSerializer
|
serializer_class = ServiceConnectionSerializer
|
||||||
|
search_fields = ["name"]
|
||||||
|
filterset_fields = ["name"]
|
||||||
|
|
||||||
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
|
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||||
@action(detail=False)
|
@action(detail=False)
|
||||||
|
|
|
@ -107,6 +107,7 @@ class PolicyViewSet(ReadOnlyModelViewSet):
|
||||||
"bindings": ["isnull"],
|
"bindings": ["isnull"],
|
||||||
"promptstage": ["isnull"],
|
"promptstage": ["isnull"],
|
||||||
}
|
}
|
||||||
|
search_fields = ["name"]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Policy.objects.select_subclasses().prefetch_related(
|
return Policy.objects.select_subclasses().prefetch_related(
|
||||||
|
|
172
swagger.yaml
172
swagger.yaml
|
@ -309,6 +309,16 @@ paths:
|
||||||
operationId: core_groups_list
|
operationId: core_groups_list
|
||||||
description: Group Viewset
|
description: Group Viewset
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: is_superuser
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -436,6 +446,26 @@ paths:
|
||||||
operationId: core_tokens_list
|
operationId: core_tokens_list
|
||||||
description: Token Viewset
|
description: Token Viewset
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: identifier
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: intent
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: user__username
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: description
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -582,6 +612,21 @@ paths:
|
||||||
operationId: core_users_list
|
operationId: core_users_list
|
||||||
description: User Viewset
|
description: User Viewset
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: username
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: is_active
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -649,6 +694,21 @@ paths:
|
||||||
operationId: core_users_me
|
operationId: core_users_me
|
||||||
description: Get information about current user
|
description: Get information about current user
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: username
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: is_active
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -2107,6 +2167,11 @@ paths:
|
||||||
operationId: outposts_service_connections_all_list
|
operationId: outposts_service_connections_all_list
|
||||||
description: ServiceConnection Viewset
|
description: ServiceConnection Viewset
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -2174,6 +2239,11 @@ paths:
|
||||||
operationId: outposts_service_connections_all_types
|
operationId: outposts_service_connections_all_types
|
||||||
description: Get all creatable service connection types
|
description: Get all creatable service connection types
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -5506,6 +5576,11 @@ paths:
|
||||||
operationId: stages_all_list
|
operationId: stages_all_list
|
||||||
description: Stage Viewset
|
description: Stage Viewset
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -5557,6 +5632,11 @@ paths:
|
||||||
operationId: stages_all_types
|
operationId: stages_all_types
|
||||||
description: Get all creatable stage types
|
description: Get all creatable stage types
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -8091,48 +8171,8 @@ definitions:
|
||||||
attributes:
|
attributes:
|
||||||
title: Attributes
|
title: Attributes
|
||||||
type: object
|
type: object
|
||||||
Token:
|
|
||||||
description: Token Serializer
|
|
||||||
required:
|
|
||||||
- identifier
|
|
||||||
- user
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
pk:
|
|
||||||
title: Token uuid
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
readOnly: true
|
|
||||||
identifier:
|
|
||||||
title: Identifier
|
|
||||||
type: string
|
|
||||||
format: slug
|
|
||||||
pattern: ^[-a-zA-Z0-9_]+$
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
intent:
|
|
||||||
title: Intent
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- verification
|
|
||||||
- api
|
|
||||||
- recovery
|
|
||||||
user:
|
|
||||||
title: User
|
|
||||||
type: integer
|
|
||||||
description:
|
|
||||||
title: Description
|
|
||||||
type: string
|
|
||||||
TokenView:
|
|
||||||
description: Show token's current key
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
key:
|
|
||||||
title: Key
|
|
||||||
type: string
|
|
||||||
readOnly: true
|
|
||||||
minLength: 1
|
|
||||||
User:
|
User:
|
||||||
|
title: User
|
||||||
description: User Serializer
|
description: User Serializer
|
||||||
required:
|
required:
|
||||||
- username
|
- username
|
||||||
|
@ -8179,6 +8219,56 @@ definitions:
|
||||||
title: Avatar
|
title: Avatar
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
attributes:
|
||||||
|
title: Attributes
|
||||||
|
type: object
|
||||||
|
Token:
|
||||||
|
description: Token Serializer
|
||||||
|
required:
|
||||||
|
- identifier
|
||||||
|
- user
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pk:
|
||||||
|
title: Token uuid
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
identifier:
|
||||||
|
title: Identifier
|
||||||
|
type: string
|
||||||
|
format: slug
|
||||||
|
pattern: ^[-a-zA-Z0-9_]+$
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
intent:
|
||||||
|
title: Intent
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- verification
|
||||||
|
- api
|
||||||
|
- recovery
|
||||||
|
user:
|
||||||
|
$ref: '#/definitions/User'
|
||||||
|
description:
|
||||||
|
title: Description
|
||||||
|
type: string
|
||||||
|
expires:
|
||||||
|
title: Expires
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
expiring:
|
||||||
|
title: Expiring
|
||||||
|
type: boolean
|
||||||
|
TokenView:
|
||||||
|
description: Show token's current key
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
title: Key
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
minLength: 1
|
||||||
CertificateKeyPair:
|
CertificateKeyPair:
|
||||||
description: CertificateKeyPair Serializer
|
description: CertificateKeyPair Serializer
|
||||||
required:
|
required:
|
||||||
|
|
|
@ -1,11 +1,43 @@
|
||||||
import { DefaultClient } from "./Client";
|
import { AKResponse, DefaultClient, QueryArguments } from "./Client";
|
||||||
|
import { User } from "./Users";
|
||||||
|
|
||||||
interface TokenResponse {
|
export enum TokenIntent {
|
||||||
key: string;
|
INTENT_VERIFICATION = "verification",
|
||||||
|
INTENT_API = "api",
|
||||||
|
INTENT_RECOVERY = "recovery",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tokenByIdentifier(identifier: string): Promise<string> {
|
export class Token {
|
||||||
return DefaultClient.fetch<TokenResponse>(["core", "tokens", identifier, "view_key"]).then(
|
|
||||||
|
pk: string;
|
||||||
|
identifier: string;
|
||||||
|
intent: TokenIntent;
|
||||||
|
user: User;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
expires: number;
|
||||||
|
expiring: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get(pk: string): Promise<User> {
|
||||||
|
return DefaultClient.fetch<User>(["core", "tokens", pk]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static list(filter?: QueryArguments): Promise<AKResponse<Token>> {
|
||||||
|
return DefaultClient.fetch<AKResponse<Token>>(["core", "tokens"], filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
static adminUrl(rest: string): string {
|
||||||
|
return `/administration/tokens/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getKey(identifier: string): Promise<string> {
|
||||||
|
return DefaultClient.fetch<{ key: string }>(["core", "tokens", identifier, "view_key"]).then(
|
||||||
(r) => r.key
|
(r) => r.key
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
|
||||||
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
|
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
|
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
|
||||||
import { tokenByIdentifier } from "../../api/Tokens";
|
import { Token } from "../../api/Tokens";
|
||||||
import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||||
|
|
||||||
@customElement("ak-token-copy-button")
|
@customElement("ak-token-copy-button")
|
||||||
|
@ -35,7 +35,7 @@ export class TokenCopyButton extends LitElement {
|
||||||
}, 1500);
|
}, 1500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tokenByIdentifier(this.identifier).then((token) => {
|
Token.getKey(this.identifier).then((token) => {
|
||||||
navigator.clipboard.writeText(token).then(() => {
|
navigator.clipboard.writeText(token).then(() => {
|
||||||
this.buttonClass = SUCCESS_CLASS;
|
this.buttonClass = SUCCESS_CLASS;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -43,7 +43,7 @@ export class TablePagination extends LitElement {
|
||||||
<button
|
<button
|
||||||
class="pf-c-button pf-m-plain"
|
class="pf-c-button pf-m-plain"
|
||||||
@click=${() => { this.pageChangeHandler(this.pages?.next || 0); }}
|
@click=${() => { this.pageChangeHandler(this.pages?.next || 0); }}
|
||||||
?disabled="${(this.pages?.next || 0) < 0}"
|
?disabled="${(this.pages?.next || 0) <= 0}"
|
||||||
aria-label="${gettext("Go to next page")}"
|
aria-label="${gettext("Go to next page")}"
|
||||||
>
|
>
|
||||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
new SidebarItem("User", "/users"),
|
new SidebarItem("User", "/users"),
|
||||||
new SidebarItem("Groups", "/groups"),
|
new SidebarItem("Groups", "/groups"),
|
||||||
new SidebarItem("Certificates", "/crypto/certificates"),
|
new SidebarItem("Certificates", "/crypto/certificates"),
|
||||||
new SidebarItem("Tokens", "/administration/tokens/"),
|
new SidebarItem("Tokens", "/tokens"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return User.me().then(u => u.is_superuser);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { gettext } from "django";
|
||||||
|
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||||
|
import { AKResponse } from "../../api/Client";
|
||||||
|
import { TablePage } from "../../elements/table/TablePage";
|
||||||
|
|
||||||
|
import "../../elements/buttons/ModalButton";
|
||||||
|
import "../../elements/buttons/Dropdown";
|
||||||
|
import { TableColumn } from "../../elements/table/Table";
|
||||||
|
import { Token } from "../../api/Tokens";
|
||||||
|
|
||||||
|
@customElement("ak-token-list")
|
||||||
|
export class TokenListPage extends TablePage<Token> {
|
||||||
|
searchEnabled(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pageTitle(): string {
|
||||||
|
return gettext("Tokens");
|
||||||
|
}
|
||||||
|
pageDescription(): string {
|
||||||
|
return gettext("Tokens are used throughout authentik for Email validation stages, Recovery keys and API access.");
|
||||||
|
}
|
||||||
|
pageIcon(): string {
|
||||||
|
return gettext("pf-icon pf-icon-security");
|
||||||
|
}
|
||||||
|
|
||||||
|
@property()
|
||||||
|
order = "expires";
|
||||||
|
|
||||||
|
apiEndpoint(page: number): Promise<AKResponse<Token>> {
|
||||||
|
return Token.list({
|
||||||
|
ordering: this.order,
|
||||||
|
page: page,
|
||||||
|
search: this.search || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
columns(): TableColumn[] {
|
||||||
|
return [
|
||||||
|
new TableColumn("Identifier", "identifier"),
|
||||||
|
new TableColumn("User", "user"),
|
||||||
|
new TableColumn("Expires?", "expiring"),
|
||||||
|
new TableColumn("Expiry date", "expires"),
|
||||||
|
new TableColumn(""),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
row(item: Token): TemplateResult[] {
|
||||||
|
return [
|
||||||
|
html`${item.identifier}`,
|
||||||
|
html`${item.user.username}`,
|
||||||
|
html`${item.expiring ? "Yes" : "No"}`,
|
||||||
|
html`${item.expiring ? new Date(item.expires * 1000).toLocaleString() : '-'}`,
|
||||||
|
html`
|
||||||
|
<ak-modal-button href="${Token.adminUrl(`${item.pk}/delete/`)}">
|
||||||
|
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||||
|
${gettext('Delete')}
|
||||||
|
</ak-spinner-button>
|
||||||
|
<div slot="modal"></div>
|
||||||
|
</ak-modal-button>
|
||||||
|
<ak-token-copy-button identifier="${item.identifier}">
|
||||||
|
${gettext('Copy Key')}
|
||||||
|
</ak-token-copy-button>
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import "./pages/sources/SourcesListPage";
|
||||||
import "./pages/sources/SourceViewPage";
|
import "./pages/sources/SourceViewPage";
|
||||||
import "./pages/groups/GroupListPage";
|
import "./pages/groups/GroupListPage";
|
||||||
import "./pages/users/UserListPage";
|
import "./pages/users/UserListPage";
|
||||||
|
import "./pages/tokens/TokenListPage";
|
||||||
import "./pages/system-tasks/SystemTaskListPage";
|
import "./pages/system-tasks/SystemTaskListPage";
|
||||||
|
|
||||||
export const ROUTES: Route[] = [
|
export const ROUTES: Route[] = [
|
||||||
|
@ -47,6 +48,7 @@ export const ROUTES: Route[] = [
|
||||||
new Route(new RegExp("^/groups$"), html`<ak-group-list></ak-group-list>`),
|
new Route(new RegExp("^/groups$"), html`<ak-group-list></ak-group-list>`),
|
||||||
new Route(new RegExp("^/users$"), html`<ak-user-list></ak-user-list>`),
|
new Route(new RegExp("^/users$"), html`<ak-user-list></ak-user-list>`),
|
||||||
new Route(new RegExp("^/flows$"), html`<ak-flow-list></ak-flow-list>`),
|
new Route(new RegExp("^/flows$"), html`<ak-flow-list></ak-flow-list>`),
|
||||||
|
new Route(new RegExp("^/tokens$"), html`<ak-token-list></ak-token-list>`),
|
||||||
new Route(new RegExp(`^/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => {
|
new Route(new RegExp(`^/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => {
|
||||||
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
|
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
|
||||||
}),
|
}),
|
||||||
|
|
Reference in New Issue