web: use generated API Client (#616)
* api: fix types for config API * api: remove broken swagger UI * admin: re-fix system task enum * events: make event optional * events: fix Schema for notification transport test * flows: use APIView for Flow Executor * core: fix schema for Metrics APIs * web: rewrite to use generated API client * web: generate API Client in CI * admin: use x_cord and y_cord to prevent yaml issues * events: fix linting errors * web: don't lint generated code * core: fix fields not being required in TypeSerializer * flows: fix missing permission_classes * web: cleanup * web: fix rendering of graph on Overview page * web: cleanup imports * core: fix missing background image filter * flows: fix flows not advancing properly * stages/*: fix warnings during get_challenge * web: send Flow response as JSON instead of FormData * web: fix styles for horizontal tabs * web: add base chart class and custom chart for application view * root: generate ts client for e2e tests * web: don't attempt to connect to websocket in selenium tests * web: fix UserTokenList not being included in the build * web: fix styling for static token list * web: fix CSRF Token missing * stages/authenticator_static: fix error when disable static tokens * core: fix display issue when updating user info * web: fix Flow executor not showing spinner when redirecting
This commit is contained in:
parent
1c6d498621
commit
2852fa3c5e
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
|
@ -59,6 +59,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
- name: prepare ts api client
|
||||||
|
run: |
|
||||||
|
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
|
||||||
- name: Docker Login Registry
|
- name: Docker Login Registry
|
||||||
env:
|
env:
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
|
@ -45,4 +45,5 @@ COPY ./lifecycle/ /lifecycle
|
||||||
USER authentik
|
USER authentik
|
||||||
STOPSIGNAL SIGINT
|
STOPSIGNAL SIGINT
|
||||||
ENV TMPDIR /dev/shm/
|
ENV TMPDIR /dev/shm/
|
||||||
|
ENV PYTHONUBUFFERED 1
|
||||||
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]
|
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]
|
||||||
|
|
|
@ -7,8 +7,8 @@ from django.db.models import Count, ExpressionWrapper, F, Model
|
||||||
from django.db.models.fields import DurationField
|
from django.db.models.fields import DurationField
|
||||||
from django.db.models.functions import ExtractHour
|
from django.db.models.functions import ExtractHour
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from drf_yasg2.utils import swagger_auto_schema
|
from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import IntegerField, SerializerMethodField
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
@ -37,23 +37,39 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
|
||||||
for hour in range(0, -24, -1):
|
for hour in range(0, -24, -1):
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
"x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
|
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple())
|
||||||
"y": data[hour * -1],
|
* 1000,
|
||||||
|
"y_cord": data[hour * -1],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
class AdministrationMetricsSerializer(Serializer):
|
class CoordinateSerializer(Serializer):
|
||||||
|
"""Coordinates for diagrams"""
|
||||||
|
|
||||||
|
x_cord = IntegerField(read_only=True)
|
||||||
|
y_cord = IntegerField(read_only=True)
|
||||||
|
|
||||||
|
def create(self, validated_data: dict) -> Model:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def update(self, instance: Model, validated_data: dict) -> Model:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class LoginMetricsSerializer(Serializer):
|
||||||
"""Login Metrics per 1h"""
|
"""Login Metrics per 1h"""
|
||||||
|
|
||||||
logins_per_1h = SerializerMethodField()
|
logins_per_1h = SerializerMethodField()
|
||||||
logins_failed_per_1h = SerializerMethodField()
|
logins_failed_per_1h = SerializerMethodField()
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
|
||||||
def get_logins_per_1h(self, _):
|
def get_logins_per_1h(self, _):
|
||||||
"""Get successful logins per hour for the last 24 hours"""
|
"""Get successful logins per hour for the last 24 hours"""
|
||||||
return get_events_per_1h(action=EventAction.LOGIN)
|
return get_events_per_1h(action=EventAction.LOGIN)
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
|
||||||
def get_logins_failed_per_1h(self, _):
|
def get_logins_failed_per_1h(self, _):
|
||||||
"""Get failed logins per hour for the last 24 hours"""
|
"""Get failed logins per hour for the last 24 hours"""
|
||||||
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
|
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
|
||||||
|
@ -70,8 +86,8 @@ class AdministrationMetricsViewSet(ViewSet):
|
||||||
|
|
||||||
permission_classes = [IsAdminUser]
|
permission_classes = [IsAdminUser]
|
||||||
|
|
||||||
@swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)})
|
@swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)})
|
||||||
def list(self, request: Request) -> Response:
|
def list(self, request: Request) -> Response:
|
||||||
"""Login Metrics per 1h"""
|
"""Login Metrics per 1h"""
|
||||||
serializer = AdministrationMetricsSerializer(True)
|
serializer = LoginMetricsSerializer(True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
|
@ -25,8 +25,8 @@ class TaskSerializer(Serializer):
|
||||||
task_finish_timestamp = DateTimeField(source="finish_timestamp")
|
task_finish_timestamp = DateTimeField(source="finish_timestamp")
|
||||||
|
|
||||||
status = ChoiceField(
|
status = ChoiceField(
|
||||||
source="result.status.value",
|
source="result.status.name",
|
||||||
choices=[(x.value, x.name) for x in TaskResultStatus],
|
choices=[(x.name, x.name) for x in TaskResultStatus],
|
||||||
)
|
)
|
||||||
messages = ListField(source="result.messages")
|
messages = ListField(source="result.messages")
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""api v2 urls"""
|
"""api v2 urls"""
|
||||||
|
from django.conf import settings
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from drf_yasg2 import openapi
|
from drf_yasg2 import openapi
|
||||||
from drf_yasg2.views import get_schema_view
|
from drf_yasg2.views import get_schema_view
|
||||||
|
@ -156,6 +157,14 @@ router.register("stages/user_write", UserWriteStageViewSet)
|
||||||
router.register("stages/dummy", DummyStageViewSet)
|
router.register("stages/dummy", DummyStageViewSet)
|
||||||
router.register("policies/dummy", DummyPolicyViewSet)
|
router.register("policies/dummy", DummyPolicyViewSet)
|
||||||
|
|
||||||
|
api_urls = router.urls + [
|
||||||
|
path(
|
||||||
|
"flows/executor/<slug:flow_slug>/",
|
||||||
|
FlowExecutorView.as_view(),
|
||||||
|
name="flow-executor",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
info = openapi.Info(
|
info = openapi.Info(
|
||||||
title="authentik API",
|
title="authentik API",
|
||||||
default_version="v2",
|
default_version="v2",
|
||||||
|
@ -165,26 +174,22 @@ info = openapi.Info(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
SchemaView = get_schema_view(
|
SchemaView = get_schema_view(
|
||||||
info,
|
info, public=True, permission_classes=(AllowAny,), patterns=api_urls
|
||||||
public=True,
|
|
||||||
permission_classes=(AllowAny,),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = api_urls + [
|
||||||
re_path(
|
re_path(
|
||||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||||
SchemaView.without_ui(cache_timeout=0),
|
SchemaView.without_ui(cache_timeout=0),
|
||||||
name="schema-json",
|
name="schema-json",
|
||||||
),
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns = urlpatterns + [
|
||||||
path(
|
path(
|
||||||
"swagger/",
|
"swagger/",
|
||||||
SchemaView.with_ui("swagger", cache_timeout=0),
|
SchemaView.with_ui("swagger", cache_timeout=0),
|
||||||
name="schema-swagger-ui",
|
name="schema-swagger-ui",
|
||||||
),
|
),
|
||||||
path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
|
]
|
||||||
path(
|
|
||||||
"flows/executor/<slug:flow_slug>/",
|
|
||||||
FlowExecutorView.as_view(),
|
|
||||||
name="flow-executor",
|
|
||||||
),
|
|
||||||
] + router.urls
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.http.response import Http404
|
from django.http.response import Http404
|
||||||
|
from drf_yasg2.utils import swagger_auto_schema
|
||||||
from guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
|
@ -13,7 +14,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||||
from rest_framework_guardian.filters import ObjectPermissionsFilter
|
from rest_framework_guardian.filters import ObjectPermissionsFilter
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.admin.api.metrics import get_events_per_1h
|
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
|
||||||
from authentik.core.api.providers import ProviderSerializer
|
from authentik.core.api.providers import ProviderSerializer
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.events.models import EventAction
|
from authentik.events.models import EventAction
|
||||||
|
@ -109,6 +110,7 @@ class ApplicationViewSet(ModelViewSet):
|
||||||
serializer = self.get_serializer(allowed_applications, many=True)
|
serializer = self.get_serializer(allowed_applications, many=True)
|
||||||
return self.get_paginated_response(serializer.data)
|
return self.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(responses={200: CoordinateSerializer(many=True)})
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
def metrics(self, request: Request, slug: str):
|
def metrics(self, request: Request, slug: str):
|
||||||
"""Metrics for application logins"""
|
"""Metrics for application logins"""
|
||||||
|
|
|
@ -28,9 +28,9 @@ class MetaNameSerializer(Serializer):
|
||||||
class TypeCreateSerializer(Serializer):
|
class TypeCreateSerializer(Serializer):
|
||||||
"""Types of an object that can be created"""
|
"""Types of an object that can be created"""
|
||||||
|
|
||||||
name = CharField(read_only=True)
|
name = CharField(required=True)
|
||||||
description = CharField(read_only=True)
|
description = CharField(required=True)
|
||||||
link = CharField(read_only=True)
|
link = CharField(required=True)
|
||||||
|
|
||||||
def create(self, validated_data: dict) -> Model:
|
def create(self, validated_data: dict) -> Model:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="pf-c-background-image">
|
<div class="pf-c-background-image">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
|
||||||
|
<filter id="image_overlay">
|
||||||
|
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
|
||||||
|
<feFuncR type="table" tableValues="0.0086274509803922 0.63921568627451"></feFuncR>
|
||||||
|
<feFuncG type="table" tableValues="0.0086274509803922 0.63921568627451"></feFuncG>
|
||||||
|
<feFuncB type="table" tableValues="0.0086274509803922 0.63921568627451"></feFuncB>
|
||||||
|
<feFuncA type="table" tableValues="0 1"></feFuncA>
|
||||||
|
</feComponentTransfer>
|
||||||
|
</filter>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<ak-message-container></ak-message-container>
|
<ak-message-container></ak-message-container>
|
||||||
<div class="pf-c-login">
|
<div class="pf-c-login">
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.contrib.auth.mixins import (
|
||||||
)
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
|
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 UpdateView
|
from django.views.generic import UpdateView
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
|
@ -34,7 +35,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||||
form_class = UserDetailForm
|
form_class = UserDetailForm
|
||||||
|
|
||||||
success_message = _("Successfully updated user.")
|
success_message = _("Successfully updated user.")
|
||||||
success_url = "/"
|
success_url = reverse_lazy("authentik_core:user-details")
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
|
@ -13,7 +13,7 @@ class NotificationSerializer(ModelSerializer):
|
||||||
|
|
||||||
body = ReadOnlyField()
|
body = ReadOnlyField()
|
||||||
severity = ReadOnlyField()
|
severity = ReadOnlyField()
|
||||||
event = EventSerializer()
|
event = EventSerializer(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
"""NotificationTransport API Views"""
|
"""NotificationTransport API Views"""
|
||||||
from django.http.response import Http404
|
from django.http.response import Http404
|
||||||
|
from drf_yasg2.utils import no_body, swagger_auto_schema
|
||||||
from guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import CharField, ListField, SerializerMethodField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer, Serializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.events.models import (
|
from authentik.events.models import (
|
||||||
|
@ -38,12 +39,28 @@ class NotificationTransportSerializer(ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationTransportTestSerializer(Serializer):
|
||||||
|
"""Notification test serializer"""
|
||||||
|
|
||||||
|
messages = ListField(child=CharField())
|
||||||
|
|
||||||
|
def create(self, request: Request) -> Response:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def update(self, request: Request) -> Response:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class NotificationTransportViewSet(ModelViewSet):
|
class NotificationTransportViewSet(ModelViewSet):
|
||||||
"""NotificationTransport Viewset"""
|
"""NotificationTransport Viewset"""
|
||||||
|
|
||||||
queryset = NotificationTransport.objects.all()
|
queryset = NotificationTransport.objects.all()
|
||||||
serializer_class = NotificationTransportSerializer
|
serializer_class = NotificationTransportSerializer
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
responses={200: NotificationTransportTestSerializer(many=False)},
|
||||||
|
request_body=no_body,
|
||||||
|
)
|
||||||
@action(detail=True, methods=["post"])
|
@action(detail=True, methods=["post"])
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def test(self, request: Request, pk=None) -> Response:
|
def test(self, request: Request, pk=None) -> Response:
|
||||||
|
@ -61,6 +78,10 @@ class NotificationTransportViewSet(ModelViewSet):
|
||||||
user=request.user,
|
user=request.user,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
return Response(transport.send(notification))
|
response = NotificationTransportTestSerializer(
|
||||||
|
data={"messages": transport.send(notification)}
|
||||||
|
)
|
||||||
|
response.is_valid()
|
||||||
|
return Response(response.data)
|
||||||
except NotificationTransportError as exc:
|
except NotificationTransportError as exc:
|
||||||
return Response(str(exc.__cause__ or None), status=503)
|
return Response(str(exc.__cause__ or None), status=503)
|
||||||
|
|
|
@ -38,7 +38,9 @@ class Challenge(Serializer):
|
||||||
"""Challenge that gets sent to the client based on which stage
|
"""Challenge that gets sent to the client based on which stage
|
||||||
is currently active"""
|
is currently active"""
|
||||||
|
|
||||||
type = ChoiceField(choices=list(ChallengeTypes))
|
type = ChoiceField(
|
||||||
|
choices=[(x.name, x.name) for x in ChallengeTypes],
|
||||||
|
)
|
||||||
component = CharField(required=False)
|
component = CharField(required=False)
|
||||||
title = CharField(required=False)
|
title = CharField(required=False)
|
||||||
|
|
||||||
|
@ -90,7 +92,7 @@ class ChallengeResponse(Serializer):
|
||||||
|
|
||||||
stage: Optional["StageView"]
|
stage: Optional["StageView"]
|
||||||
|
|
||||||
def __init__(self, instance, data, **kwargs):
|
def __init__(self, instance=None, data=None, **kwargs):
|
||||||
self.stage = kwargs.pop("stage", None)
|
self.stage = kwargs.pop("stage", None)
|
||||||
super().__init__(instance=instance, data=data, **kwargs)
|
super().__init__(instance=instance, data=data, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.http import HttpRequest
|
||||||
from django.http.request import QueryDict
|
from django.http.request import QueryDict
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
|
from rest_framework.request import Request
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import DEFAULT_AVATAR, User
|
from authentik.core.models import DEFAULT_AVATAR, User
|
||||||
|
@ -67,9 +68,9 @@ class ChallengeStageView(StageView):
|
||||||
return HttpChallengeResponse(challenge)
|
return HttpChallengeResponse(challenge)
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def post(self, request: Request, *args, **kwargs) -> HttpResponse:
|
||||||
"""Handle challenge response"""
|
"""Handle challenge response"""
|
||||||
challenge: ChallengeResponse = self.get_response_instance(data=request.POST)
|
challenge: ChallengeResponse = self.get_response_instance(data=request.data)
|
||||||
if not challenge.is_valid():
|
if not challenge.is_valid():
|
||||||
return self.challenge_invalid(challenge)
|
return self.challenge_invalid(challenge)
|
||||||
return self.challenge_valid(challenge)
|
return self.challenge_valid(challenge)
|
||||||
|
|
|
@ -9,11 +9,16 @@ from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
|
from drf_yasg2.utils import no_body, swagger_auto_schema
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from rest_framework.views import APIView
|
||||||
from structlog.stdlib import BoundLogger, get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
|
|
||||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||||
from authentik.events.models import cleanse_dict
|
from authentik.events.models import cleanse_dict
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
|
Challenge,
|
||||||
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
ChallengeTypes,
|
||||||
HttpChallengeResponse,
|
HttpChallengeResponse,
|
||||||
RedirectChallenge,
|
RedirectChallenge,
|
||||||
|
@ -40,9 +45,11 @@ SESSION_KEY_GET = "authentik_flows_get"
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(xframe_options_sameorigin, name="dispatch")
|
@method_decorator(xframe_options_sameorigin, name="dispatch")
|
||||||
class FlowExecutorView(View):
|
class FlowExecutorView(APIView):
|
||||||
"""Stage 1 Flow executor, passing requests to Stage Views"""
|
"""Stage 1 Flow executor, passing requests to Stage Views"""
|
||||||
|
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
flow: Flow
|
flow: Flow
|
||||||
|
|
||||||
plan: Optional[FlowPlan] = None
|
plan: Optional[FlowPlan] = None
|
||||||
|
@ -113,8 +120,13 @@ class FlowExecutorView(View):
|
||||||
self.current_stage_view.request = request
|
self.current_stage_view.request = request
|
||||||
return super().dispatch(request)
|
return super().dispatch(request)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
responses={200: Challenge()},
|
||||||
|
request_body=no_body,
|
||||||
|
operation_id="flows_executor_get",
|
||||||
|
)
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
"""pass get request to current stage"""
|
"""Get the next pending challenge from the currently active flow."""
|
||||||
self._logger.debug(
|
self._logger.debug(
|
||||||
"f(exec): Passing GET",
|
"f(exec): Passing GET",
|
||||||
view_class=class_to_path(self.current_stage_view.__class__),
|
view_class=class_to_path(self.current_stage_view.__class__),
|
||||||
|
@ -127,8 +139,13 @@ class FlowExecutorView(View):
|
||||||
self._logger.exception(exc)
|
self._logger.exception(exc)
|
||||||
return to_stage_response(request, FlowErrorResponse(request, exc))
|
return to_stage_response(request, FlowErrorResponse(request, exc))
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
responses={200: Challenge()},
|
||||||
|
request_body=ChallengeResponse(),
|
||||||
|
operation_id="flows_executor_solve",
|
||||||
|
)
|
||||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
"""pass post request to current stage"""
|
"""Solve the previously retrieved challenge and advanced to the next stage."""
|
||||||
self._logger.debug(
|
self._logger.debug(
|
||||||
"f(exec): Passing POST",
|
"f(exec): Passing POST",
|
||||||
view_class=class_to_path(self.current_stage_view.__class__),
|
view_class=class_to_path(self.current_stage_view.__class__),
|
||||||
|
@ -175,8 +192,10 @@ class FlowExecutorView(View):
|
||||||
"f(exec): Continuing with next stage",
|
"f(exec): Continuing with next stage",
|
||||||
reamining=len(self.plan.stages),
|
reamining=len(self.plan.stages),
|
||||||
)
|
)
|
||||||
|
kwargs = self.kwargs
|
||||||
|
kwargs.update({"flow_slug": self.flow.slug})
|
||||||
return redirect_with_qs(
|
return redirect_with_qs(
|
||||||
"authentik_api:flow-executor", self.request.GET, **self.kwargs
|
"authentik_api:flow-executor", self.request.GET, **kwargs
|
||||||
)
|
)
|
||||||
# User passed all stages
|
# User passed all stages
|
||||||
self._logger.debug(
|
self._logger.debug(
|
||||||
|
|
|
@ -74,7 +74,7 @@ class SAMLFlowFinalView(ChallengeStageView):
|
||||||
return super().get(
|
return super().get(
|
||||||
self.request,
|
self.request,
|
||||||
**{
|
**{
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-autosubmit",
|
"component": "ak-stage-autosubmit",
|
||||||
"title": "Redirecting to %(app)s..." % {"app": application.name},
|
"title": "Redirecting to %(app)s..." % {"app": application.name},
|
||||||
"url": provider.acs_url,
|
"url": provider.acs_url,
|
||||||
|
|
|
@ -31,7 +31,7 @@ class AuthenticatorStaticStageView(ChallengeStageView):
|
||||||
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
|
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
|
||||||
return AuthenticatorStaticChallenge(
|
return AuthenticatorStaticChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-authenticator-static",
|
"component": "ak-stage-authenticator-static",
|
||||||
"codes": [token.token for token in tokens],
|
"codes": [token.token for token in tokens],
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
|
||||||
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
||||||
return AuthenticatorTOTPChallenge(
|
return AuthenticatorTOTPChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-authenticator-totp",
|
"component": "ak-stage-authenticator-totp",
|
||||||
"config_url": device.config_url,
|
"config_url": device.config_url,
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
challenges = self.request.session["device_challenges"]
|
challenges = self.request.session["device_challenges"]
|
||||||
return AuthenticatorChallenge(
|
return AuthenticatorChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-authenticator-validate",
|
"component": "ak-stage-authenticator-validate",
|
||||||
"device_challenges": challenges,
|
"device_challenges": challenges,
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||||
|
|
||||||
return AuthenticatorWebAuthnChallenge(
|
return AuthenticatorWebAuthnChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-authenticator-webauthn",
|
"component": "ak-stage-authenticator-webauthn",
|
||||||
"registration": make_credential_options.registration_dict,
|
"registration": make_credential_options.registration_dict,
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ class CaptchaStageView(ChallengeStageView):
|
||||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
return CaptchaChallenge(
|
return CaptchaChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-captcha",
|
"component": "ak-stage-captcha",
|
||||||
"site_key": self.executor.current_stage.public_key,
|
"site_key": self.executor.current_stage.public_key,
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ConsentStageView(ChallengeStageView):
|
||||||
def get_challenge(self) -> Challenge:
|
def get_challenge(self) -> Challenge:
|
||||||
challenge = ConsentChallenge(
|
challenge = ConsentChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-consent",
|
"component": "ak-stage-consent",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,7 +24,7 @@ class DummyStageView(ChallengeStageView):
|
||||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
return DummyChallenge(
|
return DummyChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "",
|
"component": "",
|
||||||
"title": self.executor.current_stage.name,
|
"title": self.executor.current_stage.name,
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ class EmailStageView(ChallengeStageView):
|
||||||
|
|
||||||
def get_challenge(self) -> Challenge:
|
def get_challenge(self) -> Challenge:
|
||||||
challenge = EmailChallenge(
|
challenge = EmailChallenge(
|
||||||
data={"type": ChallengeTypes.native, "component": "ak-stage-email"}
|
data={"type": ChallengeTypes.native.value, "component": "ak-stage-email"}
|
||||||
)
|
)
|
||||||
return challenge
|
return challenge
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ class IdentificationStageView(ChallengeStageView):
|
||||||
current_stage: IdentificationStage = self.executor.current_stage
|
current_stage: IdentificationStage = self.executor.current_stage
|
||||||
challenge = IdentificationChallenge(
|
challenge = IdentificationChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-identification",
|
"component": "ak-stage-identification",
|
||||||
"primary_action": _("Log in"),
|
"primary_action": _("Log in"),
|
||||||
"input_type": "text",
|
"input_type": "text",
|
||||||
|
|
|
@ -78,7 +78,7 @@ class PasswordStageView(ChallengeStageView):
|
||||||
def get_challenge(self) -> Challenge:
|
def get_challenge(self) -> Challenge:
|
||||||
challenge = PasswordChallenge(
|
challenge = PasswordChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-password",
|
"component": "ak-stage-password",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -164,7 +164,7 @@ class PromptStageView(ChallengeStageView):
|
||||||
fields = list(self.executor.current_stage.fields.all().order_by("order"))
|
fields = list(self.executor.current_stage.fields.all().order_by("order"))
|
||||||
challenge = PromptChallenge(
|
challenge = PromptChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "ak-stage-prompt",
|
"component": "ak-stage-prompt",
|
||||||
"fields": [PromptSerializer(field).data for field in fields],
|
"fields": [PromptSerializer(field).data for field in fields],
|
||||||
},
|
},
|
||||||
|
|
|
@ -279,6 +279,7 @@ stages:
|
||||||
displayName: Build static files for e2e
|
displayName: Build static files for e2e
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
|
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
|
||||||
cd web
|
cd web
|
||||||
npm i
|
npm i
|
||||||
npm run build
|
npm run build
|
||||||
|
|
|
@ -33,7 +33,7 @@ stages:
|
||||||
- task: PublishPipelineArtifact@1
|
- task: PublishPipelineArtifact@1
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: 'outpost/pkg/'
|
targetPath: 'outpost/pkg/'
|
||||||
artifact: 'swagger_client'
|
artifact: 'go_swagger_client'
|
||||||
publishLocation: 'pipeline'
|
publishLocation: 'pipeline'
|
||||||
- stage: lint
|
- stage: lint
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -51,7 +51,7 @@ stages:
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'swagger_client'
|
artifactName: 'go_swagger_client'
|
||||||
path: "outpost/pkg/"
|
path: "outpost/pkg/"
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -70,7 +70,7 @@ stages:
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'swagger_client'
|
artifactName: 'go_swagger_client'
|
||||||
path: "outpost/pkg/"
|
path: "outpost/pkg/"
|
||||||
- task: Go@0
|
- task: Go@0
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -89,7 +89,7 @@ stages:
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'swagger_client'
|
artifactName: 'go_swagger_client'
|
||||||
path: "outpost/pkg/"
|
path: "outpost/pkg/"
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
inputs:
|
inputs:
|
||||||
|
|
165
swagger.yaml
165
swagger.yaml
|
@ -29,10 +29,7 @@ paths:
|
||||||
'200':
|
'200':
|
||||||
description: Login Metrics per 1h
|
description: Login Metrics per 1h
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
$ref: '#/definitions/LoginMetrics'
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/AdministrationMetrics'
|
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
parameters: []
|
parameters: []
|
||||||
|
@ -318,9 +315,12 @@ paths:
|
||||||
parameters: []
|
parameters: []
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: Coordinates for diagrams
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Application'
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Coordinate'
|
||||||
tags:
|
tags:
|
||||||
- core
|
- core
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -1620,17 +1620,12 @@ paths:
|
||||||
description: |-
|
description: |-
|
||||||
Send example notification using selected transport. Requires
|
Send example notification using selected transport. Requires
|
||||||
Modify permissions.
|
Modify permissions.
|
||||||
parameters:
|
parameters: []
|
||||||
- name: data
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/NotificationTransport'
|
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'200':
|
||||||
description: ''
|
description: Notification test serializer
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/NotificationTransport'
|
$ref: '#/definitions/NotificationTransportTest'
|
||||||
tags:
|
tags:
|
||||||
- events
|
- events
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -1822,6 +1817,42 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
/flows/executor/{flow_slug}/:
|
||||||
|
get:
|
||||||
|
operationId: flows_executor_get
|
||||||
|
description: Get the next pending challenge from the currently active flow.
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Challenge that gets sent to the client based on which stage
|
||||||
|
is currently active
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Challenge'
|
||||||
|
tags:
|
||||||
|
- flows
|
||||||
|
post:
|
||||||
|
operationId: flows_executor_solve
|
||||||
|
description: Solve the previously retrieved challenge and advanced to the next
|
||||||
|
stage.
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ChallengeResponse'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Challenge that gets sent to the client based on which stage
|
||||||
|
is currently active
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Challenge'
|
||||||
|
tags:
|
||||||
|
- flows
|
||||||
|
parameters:
|
||||||
|
- name: flow_slug
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
/flows/instances/:
|
/flows/instances/:
|
||||||
get:
|
get:
|
||||||
operationId: flows_instances_list
|
operationId: flows_instances_list
|
||||||
|
@ -9296,17 +9327,33 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
definitions:
|
definitions:
|
||||||
AdministrationMetrics:
|
Coordinate:
|
||||||
|
description: Coordinates for diagrams
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
x_cord:
|
||||||
|
title: X cord
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
y_cord:
|
||||||
|
title: Y cord
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
LoginMetrics:
|
||||||
description: Login Metrics per 1h
|
description: Login Metrics per 1h
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
logins_per_1h:
|
logins_per_1h:
|
||||||
title: Logins per 1h
|
description: ''
|
||||||
type: string
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Coordinate'
|
||||||
readOnly: true
|
readOnly: true
|
||||||
logins_failed_per_1h:
|
logins_failed_per_1h:
|
||||||
title: Logins failed per 1h
|
description: ''
|
||||||
type: string
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Coordinate'
|
||||||
readOnly: true
|
readOnly: true
|
||||||
Task:
|
Task:
|
||||||
description: Serialize TaskInfo and TaskResult
|
description: Serialize TaskInfo and TaskResult
|
||||||
|
@ -9332,11 +9379,11 @@ definitions:
|
||||||
format: date-time
|
format: date-time
|
||||||
status:
|
status:
|
||||||
title: Status
|
title: Status
|
||||||
type: integer
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- 1
|
- SUCCESSFUL
|
||||||
- 2
|
- WARNING
|
||||||
- 4
|
- ERROR
|
||||||
messages:
|
messages:
|
||||||
description: ''
|
description: ''
|
||||||
type: array
|
type: array
|
||||||
|
@ -9728,8 +9775,6 @@ definitions:
|
||||||
type: integer
|
type: integer
|
||||||
Notification:
|
Notification:
|
||||||
description: Notification Serializer
|
description: Notification Serializer
|
||||||
required:
|
|
||||||
- event
|
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
pk:
|
pk:
|
||||||
|
@ -9897,6 +9942,18 @@ definitions:
|
||||||
webhook_url:
|
webhook_url:
|
||||||
title: Webhook url
|
title: Webhook url
|
||||||
type: string
|
type: string
|
||||||
|
NotificationTransportTest:
|
||||||
|
description: Notification test serializer
|
||||||
|
required:
|
||||||
|
- messages
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
messages:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
Flow:
|
Flow:
|
||||||
description: Flow Serializer
|
description: Flow Serializer
|
||||||
required:
|
required:
|
||||||
|
@ -10050,6 +10107,55 @@ definitions:
|
||||||
format: uuid
|
format: uuid
|
||||||
readOnly: true
|
readOnly: true
|
||||||
uniqueItems: true
|
uniqueItems: true
|
||||||
|
ErrorDetail:
|
||||||
|
description: Serializer for rest_framework's error messages
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
- code
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
code:
|
||||||
|
title: Code
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
Challenge:
|
||||||
|
description: Challenge that gets sent to the client based on which stage is currently
|
||||||
|
active
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
title: Type
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- native
|
||||||
|
- shell
|
||||||
|
- redirect
|
||||||
|
component:
|
||||||
|
title: Component
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
title:
|
||||||
|
title: Title
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
response_errors:
|
||||||
|
title: Response errors
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/ErrorDetail'
|
||||||
|
ChallengeResponse:
|
||||||
|
description: Base class for all challenge responses
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
Cache:
|
Cache:
|
||||||
description: Generic cache stats for an object
|
description: Generic cache stats for an object
|
||||||
type: object
|
type: object
|
||||||
|
@ -10303,22 +10409,23 @@ definitions:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
TypeCreate:
|
TypeCreate:
|
||||||
description: Types of an object that can be created
|
description: Types of an object that can be created
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- description
|
||||||
|
- link
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
title: Name
|
title: Name
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
|
||||||
minLength: 1
|
minLength: 1
|
||||||
description:
|
description:
|
||||||
title: Description
|
title: Description
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
|
||||||
minLength: 1
|
minLength: 1
|
||||||
link:
|
link:
|
||||||
title: Link
|
title: Link
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
|
||||||
minLength: 1
|
minLength: 1
|
||||||
ServiceConnectionState:
|
ServiceConnectionState:
|
||||||
description: Serializer for Service connection state
|
description: Serializer for Service connection state
|
||||||
|
|
|
@ -4,3 +4,8 @@ node_modules
|
||||||
dist
|
dist
|
||||||
# don't lint nyc coverage output
|
# don't lint nyc coverage output
|
||||||
coverage
|
coverage
|
||||||
|
# don't lint generated code
|
||||||
|
src/api/apis
|
||||||
|
src/api/models
|
||||||
|
src/api/index.ts
|
||||||
|
src/api/runtime.ts
|
||||||
|
|
|
@ -10,6 +10,25 @@ variables:
|
||||||
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
|
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- stage: generate
|
||||||
|
jobs:
|
||||||
|
- job: swagger_generate
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- task: NodeTool@0
|
||||||
|
inputs:
|
||||||
|
versionSpec: '12.x'
|
||||||
|
displayName: 'Install Node.js'
|
||||||
|
- task: CmdLine@2
|
||||||
|
inputs:
|
||||||
|
script: |
|
||||||
|
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
|
||||||
|
- task: PublishPipelineArtifact@1
|
||||||
|
inputs:
|
||||||
|
targetPath: 'web/src/api/'
|
||||||
|
artifact: 'ts_swagger_client'
|
||||||
|
publishLocation: 'pipeline'
|
||||||
- stage: lint
|
- stage: lint
|
||||||
jobs:
|
jobs:
|
||||||
- job: eslint
|
- job: eslint
|
||||||
|
@ -20,6 +39,11 @@ stages:
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '12.x'
|
versionSpec: '12.x'
|
||||||
displayName: 'Install Node.js'
|
displayName: 'Install Node.js'
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'ts_swagger_client'
|
||||||
|
path: "web/src/api/"
|
||||||
- task: Npm@1
|
- task: Npm@1
|
||||||
inputs:
|
inputs:
|
||||||
command: 'install'
|
command: 'install'
|
||||||
|
@ -37,6 +61,11 @@ stages:
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '12.x'
|
versionSpec: '12.x'
|
||||||
displayName: 'Install Node.js'
|
displayName: 'Install Node.js'
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'ts_swagger_client'
|
||||||
|
path: "web/src/api/"
|
||||||
- task: Npm@1
|
- task: Npm@1
|
||||||
inputs:
|
inputs:
|
||||||
command: 'install'
|
command: 'install'
|
||||||
|
@ -56,6 +85,11 @@ stages:
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '12.x'
|
versionSpec: '12.x'
|
||||||
displayName: 'Install Node.js'
|
displayName: 'Install Node.js'
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'ts_swagger_client'
|
||||||
|
path: "web/src/api/"
|
||||||
- task: Npm@1
|
- task: Npm@1
|
||||||
inputs:
|
inputs:
|
||||||
command: 'install'
|
command: 'install'
|
||||||
|
@ -71,6 +105,11 @@ stages:
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-latest'
|
vmImage: 'ubuntu-latest'
|
||||||
steps:
|
steps:
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'ts_swagger_client'
|
||||||
|
path: "web/src/api/"
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
targetType: 'inline'
|
||||||
|
|
4
web/src/api/.gitignore
vendored
Normal file
4
web/src/api/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
apis/**
|
||||||
|
models/**
|
||||||
|
index.ts
|
||||||
|
runtime.ts
|
23
web/src/api/.openapi-generator-ignore
Normal file
23
web/src/api/.openapi-generator-ignore
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# OpenAPI Generator Ignore
|
||||||
|
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||||
|
|
||||||
|
# Use this file to prevent files from being overwritten by the generator.
|
||||||
|
# The patterns follow closely to .gitignore or .dockerignore.
|
||||||
|
|
||||||
|
# As an example, the C# client generator defines ApiClient.cs.
|
||||||
|
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||||
|
#ApiClient.cs
|
||||||
|
|
||||||
|
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||||
|
#foo/*/qux
|
||||||
|
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||||
|
#foo/**/qux
|
||||||
|
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can also negate patterns with an exclamation (!).
|
||||||
|
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||||
|
#docs/*.md
|
||||||
|
# Then explicitly reverse the ignore rule for a single file:
|
||||||
|
#!docs/README.md
|
167
web/src/api/.openapi-generator/FILES
Normal file
167
web/src/api/.openapi-generator/FILES
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
apis/AdminApi.ts
|
||||||
|
apis/CoreApi.ts
|
||||||
|
apis/CryptoApi.ts
|
||||||
|
apis/EventsApi.ts
|
||||||
|
apis/FlowsApi.ts
|
||||||
|
apis/OutpostsApi.ts
|
||||||
|
apis/PoliciesApi.ts
|
||||||
|
apis/PropertymappingsApi.ts
|
||||||
|
apis/ProvidersApi.ts
|
||||||
|
apis/RootApi.ts
|
||||||
|
apis/SourcesApi.ts
|
||||||
|
apis/StagesApi.ts
|
||||||
|
apis/index.ts
|
||||||
|
index.ts
|
||||||
|
models/Application.ts
|
||||||
|
models/AuthenticateWebAuthnStage.ts
|
||||||
|
models/AuthenticatorStaticStage.ts
|
||||||
|
models/AuthenticatorTOTPStage.ts
|
||||||
|
models/AuthenticatorValidateStage.ts
|
||||||
|
models/Cache.ts
|
||||||
|
models/CaptchaStage.ts
|
||||||
|
models/CertificateData.ts
|
||||||
|
models/CertificateKeyPair.ts
|
||||||
|
models/Challenge.ts
|
||||||
|
models/Config.ts
|
||||||
|
models/ConsentStage.ts
|
||||||
|
models/Coordinate.ts
|
||||||
|
models/DenyStage.ts
|
||||||
|
models/DockerServiceConnection.ts
|
||||||
|
models/DummyPolicy.ts
|
||||||
|
models/DummyStage.ts
|
||||||
|
models/EmailStage.ts
|
||||||
|
models/ErrorDetail.ts
|
||||||
|
models/Event.ts
|
||||||
|
models/EventMatcherPolicy.ts
|
||||||
|
models/EventTopPerUser.ts
|
||||||
|
models/ExpressionPolicy.ts
|
||||||
|
models/Flow.ts
|
||||||
|
models/FlowDiagram.ts
|
||||||
|
models/FlowStageBinding.ts
|
||||||
|
models/Group.ts
|
||||||
|
models/GroupMembershipPolicy.ts
|
||||||
|
models/HaveIBeenPwendPolicy.ts
|
||||||
|
models/IPReputation.ts
|
||||||
|
models/IdentificationStage.ts
|
||||||
|
models/InlineResponse200.ts
|
||||||
|
models/InlineResponse2001.ts
|
||||||
|
models/InlineResponse20010.ts
|
||||||
|
models/InlineResponse20011.ts
|
||||||
|
models/InlineResponse20012.ts
|
||||||
|
models/InlineResponse20013.ts
|
||||||
|
models/InlineResponse20014.ts
|
||||||
|
models/InlineResponse20015.ts
|
||||||
|
models/InlineResponse20016.ts
|
||||||
|
models/InlineResponse20017.ts
|
||||||
|
models/InlineResponse20018.ts
|
||||||
|
models/InlineResponse20019.ts
|
||||||
|
models/InlineResponse2002.ts
|
||||||
|
models/InlineResponse20020.ts
|
||||||
|
models/InlineResponse20021.ts
|
||||||
|
models/InlineResponse20022.ts
|
||||||
|
models/InlineResponse20023.ts
|
||||||
|
models/InlineResponse20024.ts
|
||||||
|
models/InlineResponse20025.ts
|
||||||
|
models/InlineResponse20026.ts
|
||||||
|
models/InlineResponse20027.ts
|
||||||
|
models/InlineResponse20028.ts
|
||||||
|
models/InlineResponse20029.ts
|
||||||
|
models/InlineResponse2003.ts
|
||||||
|
models/InlineResponse20030.ts
|
||||||
|
models/InlineResponse20031.ts
|
||||||
|
models/InlineResponse20032.ts
|
||||||
|
models/InlineResponse20033.ts
|
||||||
|
models/InlineResponse20034.ts
|
||||||
|
models/InlineResponse20035.ts
|
||||||
|
models/InlineResponse20036.ts
|
||||||
|
models/InlineResponse20037.ts
|
||||||
|
models/InlineResponse20038.ts
|
||||||
|
models/InlineResponse20039.ts
|
||||||
|
models/InlineResponse2004.ts
|
||||||
|
models/InlineResponse20040.ts
|
||||||
|
models/InlineResponse20041.ts
|
||||||
|
models/InlineResponse20042.ts
|
||||||
|
models/InlineResponse20043.ts
|
||||||
|
models/InlineResponse20044.ts
|
||||||
|
models/InlineResponse20045.ts
|
||||||
|
models/InlineResponse20046.ts
|
||||||
|
models/InlineResponse20047.ts
|
||||||
|
models/InlineResponse20048.ts
|
||||||
|
models/InlineResponse20049.ts
|
||||||
|
models/InlineResponse2005.ts
|
||||||
|
models/InlineResponse20050.ts
|
||||||
|
models/InlineResponse20051.ts
|
||||||
|
models/InlineResponse20052.ts
|
||||||
|
models/InlineResponse20053.ts
|
||||||
|
models/InlineResponse20054.ts
|
||||||
|
models/InlineResponse20055.ts
|
||||||
|
models/InlineResponse20056.ts
|
||||||
|
models/InlineResponse20057.ts
|
||||||
|
models/InlineResponse20058.ts
|
||||||
|
models/InlineResponse20059.ts
|
||||||
|
models/InlineResponse2006.ts
|
||||||
|
models/InlineResponse20060.ts
|
||||||
|
models/InlineResponse2007.ts
|
||||||
|
models/InlineResponse2008.ts
|
||||||
|
models/InlineResponse2009.ts
|
||||||
|
models/InlineResponse200Pagination.ts
|
||||||
|
models/Invitation.ts
|
||||||
|
models/InvitationStage.ts
|
||||||
|
models/KubernetesServiceConnection.ts
|
||||||
|
models/LDAPPropertyMapping.ts
|
||||||
|
models/LDAPSource.ts
|
||||||
|
models/LDAPSourceSyncStatus.ts
|
||||||
|
models/LoginMetrics.ts
|
||||||
|
models/Notification.ts
|
||||||
|
models/NotificationRule.ts
|
||||||
|
models/NotificationRuleGroup.ts
|
||||||
|
models/NotificationRuleGroupParent.ts
|
||||||
|
models/NotificationRuleTransports.ts
|
||||||
|
models/NotificationTransport.ts
|
||||||
|
models/NotificationTransportTest.ts
|
||||||
|
models/OAuth2Provider.ts
|
||||||
|
models/OAuth2ProviderSetupURLs.ts
|
||||||
|
models/OAuthSource.ts
|
||||||
|
models/OpenIDConnectConfiguration.ts
|
||||||
|
models/Outpost.ts
|
||||||
|
models/OutpostHealth.ts
|
||||||
|
models/PasswordExpiryPolicy.ts
|
||||||
|
models/PasswordPolicy.ts
|
||||||
|
models/PasswordStage.ts
|
||||||
|
models/Policy.ts
|
||||||
|
models/PolicyBinding.ts
|
||||||
|
models/PolicyBindingPolicy.ts
|
||||||
|
models/PolicyBindingUser.ts
|
||||||
|
models/PolicyBindingUserAkGroups.ts
|
||||||
|
models/PolicyBindingUserGroups.ts
|
||||||
|
models/PolicyBindingUserSources.ts
|
||||||
|
models/PolicyBindingUserUserPermissions.ts
|
||||||
|
models/Prompt.ts
|
||||||
|
models/PromptStage.ts
|
||||||
|
models/PropertyMapping.ts
|
||||||
|
models/Provider.ts
|
||||||
|
models/ProxyOutpostConfig.ts
|
||||||
|
models/ProxyProvider.ts
|
||||||
|
models/ReputationPolicy.ts
|
||||||
|
models/SAMLMetadata.ts
|
||||||
|
models/SAMLPropertyMapping.ts
|
||||||
|
models/SAMLProvider.ts
|
||||||
|
models/SAMLSource.ts
|
||||||
|
models/ScopeMapping.ts
|
||||||
|
models/ServiceConnection.ts
|
||||||
|
models/ServiceConnectionState.ts
|
||||||
|
models/Source.ts
|
||||||
|
models/Stage.ts
|
||||||
|
models/Task.ts
|
||||||
|
models/Token.ts
|
||||||
|
models/TokenView.ts
|
||||||
|
models/TypeCreate.ts
|
||||||
|
models/User.ts
|
||||||
|
models/UserDeleteStage.ts
|
||||||
|
models/UserLoginStage.ts
|
||||||
|
models/UserLogoutStage.ts
|
||||||
|
models/UserReputation.ts
|
||||||
|
models/UserWriteStage.ts
|
||||||
|
models/Version.ts
|
||||||
|
models/index.ts
|
||||||
|
runtime.ts
|
1
web/src/api/.openapi-generator/VERSION
Normal file
1
web/src/api/.openapi-generator/VERSION
Normal file
|
@ -0,0 +1 @@
|
||||||
|
5.1.0-SNAPSHOT
|
|
@ -1,32 +0,0 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
|
|
||||||
import { Provider } from "./Providers";
|
|
||||||
|
|
||||||
export class Application {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
provider?: Provider;
|
|
||||||
|
|
||||||
launch_url: string;
|
|
||||||
meta_launch_url: string;
|
|
||||||
meta_icon: string;
|
|
||||||
meta_description: string;
|
|
||||||
meta_publisher: string;
|
|
||||||
policies: string[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<Application> {
|
|
||||||
return DefaultClient.fetch<Application>(["core", "applications", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Application>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Application>>(["core", "applications"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/applications/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
|
|
||||||
|
|
||||||
export class CertificateKeyPair {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
fingerprint: string;
|
|
||||||
cert_expiry: number;
|
|
||||||
cert_subject: string;
|
|
||||||
private_key_available: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<CertificateKeyPair> {
|
|
||||||
return DefaultClient.fetch<CertificateKeyPair>(["crypto", "certificatekeypairs", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<CertificateKeyPair>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<CertificateKeyPair>>(["crypto", "certificatekeypairs"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/crypto/certificates/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,3 @@
|
||||||
import { gettext } from "django";
|
|
||||||
import { showMessage } from "../elements/messages/MessageContainer";
|
|
||||||
import { getCookie } from "../utils";
|
|
||||||
import { NotFoundError, RequestError } from "./Error";
|
|
||||||
|
|
||||||
export const VERSION = "v2beta";
|
|
||||||
|
|
||||||
export interface QueryArguments {
|
export interface QueryArguments {
|
||||||
page?: number;
|
page?: number;
|
||||||
page_size?: number;
|
page_size?: number;
|
||||||
|
@ -20,97 +13,20 @@ export interface BaseInheritanceModel {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Client {
|
export interface AKPagination {
|
||||||
makeUrl(url: string[], query?: QueryArguments): string {
|
|
||||||
let builtUrl = `/api/${VERSION}/${url.join("/")}/`;
|
|
||||||
if (query) {
|
|
||||||
const queryString = Object.keys(query)
|
|
||||||
.filter((k) => query[k] !== null)
|
|
||||||
// we default to a string in query[k] as we've filtered out the null above
|
|
||||||
// this is just for type-hinting
|
|
||||||
.map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(query[k] || ""))
|
|
||||||
.join("&");
|
|
||||||
builtUrl += `?${queryString}`;
|
|
||||||
}
|
|
||||||
return builtUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch<T>(url: string[], query?: QueryArguments): Promise<T> {
|
|
||||||
const finalUrl = this.makeUrl(url, query);
|
|
||||||
return fetch(finalUrl)
|
|
||||||
.then((r) => {
|
|
||||||
if (r.status > 300) {
|
|
||||||
switch (r.status) {
|
|
||||||
case 404:
|
|
||||||
throw new NotFoundError(`URL ${finalUrl} not found`);
|
|
||||||
default:
|
|
||||||
throw new RequestError(r.statusText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
showMessage({
|
|
||||||
level_tag: "error",
|
|
||||||
message: gettext(`Unexpected error while fetching: ${e.toString()}`),
|
|
||||||
});
|
|
||||||
return e;
|
|
||||||
})
|
|
||||||
.then((r) => r.json())
|
|
||||||
.then((r) => <T>r);
|
|
||||||
}
|
|
||||||
|
|
||||||
private writeRequest<T>(url: string[], body: T, method: string, query?: QueryArguments): Promise<T> {
|
|
||||||
const finalUrl = this.makeUrl(url, query);
|
|
||||||
const csrftoken = getCookie("authentik_csrf");
|
|
||||||
const request = new Request(finalUrl, {
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-CSRFToken": csrftoken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return fetch(request, {
|
|
||||||
method: method,
|
|
||||||
mode: "same-origin",
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
})
|
|
||||||
.then((r) => {
|
|
||||||
if (r.status > 300) {
|
|
||||||
switch (r.status) {
|
|
||||||
case 404:
|
|
||||||
throw new NotFoundError(`URL ${finalUrl} not found`);
|
|
||||||
default:
|
|
||||||
throw new RequestError(r.statusText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
})
|
|
||||||
.then((r) => r.json())
|
|
||||||
.then((r) => <T>r);
|
|
||||||
}
|
|
||||||
|
|
||||||
update<T>(url: string[], body: T, query?: QueryArguments): Promise<T> {
|
|
||||||
return this.writeRequest(url, body, "PATCH", query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DefaultClient = new Client();
|
|
||||||
|
|
||||||
export interface PBPagination {
|
|
||||||
next?: number;
|
next?: number;
|
||||||
previous?: number;
|
previous?: number;
|
||||||
|
|
||||||
count: number;
|
count: number;
|
||||||
current: number;
|
current: number;
|
||||||
total_pages: number;
|
totalPages: number;
|
||||||
|
|
||||||
start_index: number;
|
startIndex: number;
|
||||||
end_index: number;
|
endIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AKResponse<T> {
|
export interface AKResponse<T> {
|
||||||
pagination: PBPagination;
|
pagination: AKPagination;
|
||||||
|
|
||||||
results: Array<T>;
|
results: Array<T>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
import { DefaultClient } from "./Client";
|
|
||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import { Integrations } from "@sentry/tracing";
|
import { Integrations } from "@sentry/tracing";
|
||||||
import { VERSION } from "../constants";
|
import { VERSION } from "../constants";
|
||||||
import { SentryIgnoredError } from "../common/errors";
|
import { SentryIgnoredError } from "../common/errors";
|
||||||
|
import { Configuration } from "./runtime";
|
||||||
|
import { RootApi } from "./apis";
|
||||||
|
import { Config } from ".";
|
||||||
|
import { getCookie } from "../utils";
|
||||||
|
|
||||||
export class Config {
|
export const DEFAULT_CONFIG = new Configuration({
|
||||||
branding_logo: string;
|
basePath: "/api/v2beta",
|
||||||
branding_title: string;
|
headers: {
|
||||||
|
"X-CSRFToken": getCookie("authentik_csrf"),
|
||||||
error_reporting_enabled: boolean;
|
|
||||||
error_reporting_environment: string;
|
|
||||||
error_reporting_send_pii: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
static get(): Promise<Config> {
|
export function configureSentry(): Promise<Config> {
|
||||||
return DefaultClient.fetch<Config>(["root", "config"]).then((config) => {
|
return new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => {
|
||||||
if (config.error_reporting_enabled) {
|
if (config.errorReportingEnabled) {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
|
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
|
||||||
release: `authentik@${VERSION}`,
|
release: `authentik@${VERSION}`,
|
||||||
|
@ -26,7 +24,7 @@ export class Config {
|
||||||
new Integrations.BrowserTracing(),
|
new Integrations.BrowserTracing(),
|
||||||
],
|
],
|
||||||
tracesSampleRate: 0.6,
|
tracesSampleRate: 0.6,
|
||||||
environment: config.error_reporting_environment,
|
environment: config.errorReportingEnvironment,
|
||||||
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
|
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
|
||||||
if (hint.originalException instanceof SentryIgnoredError) {
|
if (hint.originalException instanceof SentryIgnoredError) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -39,4 +37,3 @@ export class Config {
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
|
|
||||||
import { Event } from "./Events";
|
|
||||||
|
|
||||||
export class Notification {
|
|
||||||
pk: string;
|
|
||||||
severity: string;
|
|
||||||
body: string;
|
|
||||||
created: string;
|
|
||||||
event?: Event;
|
|
||||||
seen: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Notification> {
|
|
||||||
return DefaultClient.fetch<Notification>(["events", "notifications", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Notification>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Notification>>(["events", "notifications"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static markSeen(pk: string): Promise<{seen: boolean}> {
|
|
||||||
return DefaultClient.update(["events", "notifications", pk], {
|
|
||||||
"seen": true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
|
|
||||||
import { Group } from "./Groups";
|
|
||||||
|
|
||||||
export class Rule {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
transports: string[];
|
|
||||||
severity: string;
|
|
||||||
group?: Group;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Rule> {
|
|
||||||
return DefaultClient.fetch<Rule>(["events", "rules", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Rule>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Rule>>(["events", "rules"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/events/rules/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
|
|
||||||
|
|
||||||
export class Transport {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
mode: string;
|
|
||||||
mode_verbose: string;
|
|
||||||
webhook_url: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Transport> {
|
|
||||||
return DefaultClient.fetch<Transport>(["events", "transports", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Transport>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Transport>>(["events", "transports"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/events/transports/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
|
import { Event } from "./models";
|
||||||
|
|
||||||
export interface EventUser {
|
export interface EventUser {
|
||||||
pk: number;
|
pk: number;
|
||||||
|
@ -11,37 +11,7 @@ export interface EventContext {
|
||||||
[key: string]: EventContext | string | number | string[];
|
[key: string]: EventContext | string | number | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Event {
|
export interface EventWithContext extends Event {
|
||||||
pk: string;
|
|
||||||
user: EventUser;
|
user: EventUser;
|
||||||
action: string;
|
|
||||||
app: string;
|
|
||||||
context: EventContext;
|
context: EventContext;
|
||||||
client_ip: string;
|
|
||||||
created: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Event> {
|
|
||||||
return DefaultClient.fetch<Event>(["events", "events", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Event>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Event>>(["events", "events"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// events/events/top_per_user/?filter_action=authorize_application
|
|
||||||
static topForUser(action: string): Promise<TopNEvent[]> {
|
|
||||||
return DefaultClient.fetch<TopNEvent[]>(["events", "events", "top_per_user"], {
|
|
||||||
"filter_action": action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TopNEvent {
|
|
||||||
application: { [key: string]: string};
|
|
||||||
counted_events: number;
|
|
||||||
unique_users: number;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments, BaseInheritanceModel } from "./Client";
|
import { ChallengeTypeEnum } from "./models";
|
||||||
import { TypeCreate } from "./Providers";
|
|
||||||
|
|
||||||
export enum ChallengeTypes {
|
|
||||||
native = "native",
|
|
||||||
response = "response",
|
|
||||||
shell = "shell",
|
|
||||||
redirect = "redirect",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Error {
|
export interface Error {
|
||||||
code: string;
|
code: string;
|
||||||
|
@ -18,11 +10,12 @@ export interface ErrorDict {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Challenge {
|
export interface Challenge {
|
||||||
type: ChallengeTypes;
|
type: ChallengeTypeEnum;
|
||||||
component?: string;
|
component?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
response_errors?: ErrorDict;
|
response_errors?: ErrorDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WithUserInfoChallenge extends Challenge {
|
export interface WithUserInfoChallenge extends Challenge {
|
||||||
pending_user: string;
|
pending_user: string;
|
||||||
pending_user_avatar: string;
|
pending_user_avatar: string;
|
||||||
|
@ -31,6 +24,7 @@ export interface WithUserInfoChallenge extends Challenge {
|
||||||
export interface ShellChallenge extends Challenge {
|
export interface ShellChallenge extends Challenge {
|
||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedirectChallenge extends Challenge {
|
export interface RedirectChallenge extends Challenge {
|
||||||
to: string;
|
to: string;
|
||||||
}
|
}
|
||||||
|
@ -44,104 +38,3 @@ export enum FlowDesignation {
|
||||||
Recovery = "recovery",
|
Recovery = "recovery",
|
||||||
StageConfiguration = "stage_configuration",
|
StageConfiguration = "stage_configuration",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Flow {
|
|
||||||
pk: string;
|
|
||||||
policybindingmodel_ptr_id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
title: string;
|
|
||||||
designation: FlowDesignation;
|
|
||||||
background: string;
|
|
||||||
stages: string[];
|
|
||||||
policies: string[];
|
|
||||||
cache_count: number;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<Flow> {
|
|
||||||
return DefaultClient.fetch<Flow>(["flows", "instances", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static diagram(slug: string): Promise<{ diagram: string }> {
|
|
||||||
return DefaultClient.fetch<{ diagram: string }>(["flows", "instances", slug, "diagram"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Flow>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Flow>>(["flows", "instances"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static cached(): Promise<number> {
|
|
||||||
return DefaultClient.fetch<{ count: number }>(["flows", "instances", "cached"]).then(r => {
|
|
||||||
return r.count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static executor(slug: string): Promise<Challenge> {
|
|
||||||
return DefaultClient.fetch(["flows", "executor", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/flows/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Stage implements BaseInheritanceModel {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
object_type: string;
|
|
||||||
verbose_name: string;
|
|
||||||
verbose_name_plural: string;
|
|
||||||
flow_set: Flow[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<Stage> {
|
|
||||||
return DefaultClient.fetch<Stage>(["stages", "all", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Stage>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Stage>>(["stages", "all"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTypes(): Promise<TypeCreate[]> {
|
|
||||||
return DefaultClient.fetch<TypeCreate[]>(["stages", "all", "types"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/stages/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FlowStageBinding {
|
|
||||||
|
|
||||||
pk: string;
|
|
||||||
policybindingmodel_ptr_id: string;
|
|
||||||
target: string;
|
|
||||||
stage: string;
|
|
||||||
stage_obj: Stage;
|
|
||||||
evaluate_on_plan: boolean;
|
|
||||||
re_evaluate_policies: boolean;
|
|
||||||
order: number;
|
|
||||||
policies: string[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<FlowStageBinding> {
|
|
||||||
return DefaultClient.fetch<FlowStageBinding>(["flows", "bindings", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<FlowStageBinding>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<FlowStageBinding>>(["flows", "bindings"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/stages/bindings/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
|
|
||||||
import { EventContext } from "./Events";
|
|
||||||
|
|
||||||
export class Group {
|
|
||||||
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
is_superuser: boolean;
|
|
||||||
attributes: EventContext;
|
|
||||||
parent?: Group;
|
|
||||||
users: number[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Group> {
|
|
||||||
return DefaultClient.fetch<Group>(["core", "groups", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Group>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Group>>(["core", "groups"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/groups/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
|
|
||||||
import { EventContext } from "./Events";
|
|
||||||
import { User } from "./Users";
|
|
||||||
|
|
||||||
export class Invitation {
|
|
||||||
|
|
||||||
pk: string;
|
|
||||||
expires: number;
|
|
||||||
fixed_date: EventContext;
|
|
||||||
created_by: User;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Invitation> {
|
|
||||||
return DefaultClient.fetch<Invitation>(["stages", "invitation", "invitations", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Invitation>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Invitation>>(["stages", "invitation", "invitations"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/stages/invitations/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
|
|
||||||
import { Provider, TypeCreate } from "./Providers";
|
|
||||||
|
|
||||||
export interface OutpostHealth {
|
|
||||||
last_seen: number;
|
|
||||||
version: string;
|
|
||||||
version_should: string;
|
|
||||||
version_outdated: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Outpost {
|
|
||||||
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
providers: number[];
|
|
||||||
providers_obj: Provider[];
|
|
||||||
service_connection?: string;
|
|
||||||
_config: QueryArguments;
|
|
||||||
token_identifier: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Outpost> {
|
|
||||||
return DefaultClient.fetch<Outpost>(["outposts", "outposts", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Outpost>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Outpost>>(["outposts", "outposts"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static health(pk: string): Promise<OutpostHealth[]> {
|
|
||||||
return DefaultClient.fetch<OutpostHealth[]>(["outposts", "outposts", pk, "health"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/outposts/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OutpostServiceConnectionState {
|
|
||||||
version: string;
|
|
||||||
healthy: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OutpostServiceConnection {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
local: boolean;
|
|
||||||
object_type: string;
|
|
||||||
verbose_name: string;
|
|
||||||
verbose_name_plural: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<OutpostServiceConnection> {
|
|
||||||
return DefaultClient.fetch<OutpostServiceConnection>(["outposts", "service_connections", "all", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<OutpostServiceConnection>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<OutpostServiceConnection>>(["outposts", "service_connections", "all"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static state(pk: string): Promise<OutpostServiceConnectionState> {
|
|
||||||
return DefaultClient.fetch<OutpostServiceConnectionState>(["outposts", "service_connections", "all", pk, "state"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTypes(): Promise<TypeCreate[]> {
|
|
||||||
return DefaultClient.fetch<TypeCreate[]>(["outposts", "service_connections", "all", "types"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/outpost_service_connections/${rest}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client";
|
|
||||||
import { TypeCreate } from "./Providers";
|
|
||||||
|
|
||||||
export class Policy implements BaseInheritanceModel {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
execution_logging: boolean;
|
|
||||||
object_type: string;
|
|
||||||
verbose_name: string;
|
|
||||||
verbose_name_plural: string;
|
|
||||||
bound_to: number;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Policy> {
|
|
||||||
return DefaultClient.fetch<Policy>(["policies", "all", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Policy>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Policy>>(["policies", "all"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static cached(): Promise<number> {
|
|
||||||
return DefaultClient.fetch<{ count: number }>(["policies", "all", "cached"]).then(r => {
|
|
||||||
return r.count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTypes(): Promise<TypeCreate[]> {
|
|
||||||
return DefaultClient.fetch<TypeCreate[]>(["policies", "all", "types"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/policies/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
|
|
||||||
import { Group } from "./Groups";
|
|
||||||
import { Policy } from "./Policies";
|
|
||||||
import { User } from "./Users";
|
|
||||||
|
|
||||||
export class PolicyBinding {
|
|
||||||
pk: string;
|
|
||||||
policy?: Policy;
|
|
||||||
group?: Group;
|
|
||||||
user?: User;
|
|
||||||
target: string;
|
|
||||||
enabled: boolean;
|
|
||||||
order: number;
|
|
||||||
timeout: number;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<PolicyBinding> {
|
|
||||||
return DefaultClient.fetch<PolicyBinding>(["policies", "bindings", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<PolicyBinding>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<PolicyBinding>>(["policies", "bindings"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/policies/bindings/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
|
|
||||||
import { Stage } from "./Flows";
|
|
||||||
|
|
||||||
export class Prompt {
|
|
||||||
|
|
||||||
pk: string;
|
|
||||||
field_key: string;
|
|
||||||
label: string;
|
|
||||||
type: string;
|
|
||||||
required: boolean;
|
|
||||||
placeholder: string;
|
|
||||||
order: number;
|
|
||||||
promptstage_set: Stage[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<Prompt> {
|
|
||||||
return DefaultClient.fetch<Prompt>(["stages", "prompt", "prompts", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Prompt>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Prompt>>(["stages", "prompt", "prompts"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/stages_prompts/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
|
|
||||||
import { TypeCreate } from "./Providers";
|
|
||||||
|
|
||||||
export class PropertyMapping {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
expression: string;
|
|
||||||
|
|
||||||
verbose_name: string;
|
|
||||||
verbose_name_plural: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<PropertyMapping> {
|
|
||||||
return DefaultClient.fetch<PropertyMapping>(["propertymappings", "all", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<PropertyMapping>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<PropertyMapping>>(["propertymappings", "all"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTypes(): Promise<TypeCreate[]> {
|
|
||||||
return DefaultClient.fetch<TypeCreate[]>(["propertymappings", "all", "types"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/property-mappings/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
|
|
||||||
|
|
||||||
export interface TypeCreate {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
link: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Provider implements BaseInheritanceModel {
|
|
||||||
pk: number;
|
|
||||||
name: string;
|
|
||||||
authorization_flow: string;
|
|
||||||
object_type: string;
|
|
||||||
|
|
||||||
assigned_application_slug?: string;
|
|
||||||
assigned_application_name?: string;
|
|
||||||
|
|
||||||
verbose_name: string;
|
|
||||||
verbose_name_plural: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(id: number): Promise<Provider> {
|
|
||||||
return DefaultClient.fetch<Provider>(["providers", "all", id.toString()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Provider>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Provider>>(["providers", "all"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTypes(): Promise<TypeCreate[]> {
|
|
||||||
return DefaultClient.fetch<TypeCreate[]>(["providers", "all", "types"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/providers/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
|
|
||||||
import { TypeCreate } from "./Providers";
|
|
||||||
|
|
||||||
export class Source implements BaseInheritanceModel {
|
|
||||||
pk: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
enabled: boolean;
|
|
||||||
authentication_flow: string;
|
|
||||||
enrollment_flow: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
object_type: string;
|
|
||||||
verbose_name: string;
|
|
||||||
verbose_name_plural: string;
|
|
||||||
|
|
||||||
static get(slug: string): Promise<Source> {
|
|
||||||
return DefaultClient.fetch<Source>(["sources", "all", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<Source>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<Source>>(["sources", "all"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTypes(): Promise<TypeCreate[]> {
|
|
||||||
return DefaultClient.fetch<TypeCreate[]>(["sources", "all", "types"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/sources/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { DefaultClient, QueryArguments } from "./Client";
|
|
||||||
|
|
||||||
export enum TaskStatus {
|
|
||||||
SUCCESSFUL = 1,
|
|
||||||
WARNING = 2,
|
|
||||||
ERROR = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SystemTask {
|
|
||||||
|
|
||||||
task_name: string;
|
|
||||||
task_description: string;
|
|
||||||
task_finish_timestamp: number;
|
|
||||||
status: TaskStatus;
|
|
||||||
messages: string[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(task_name: string): Promise<SystemTask> {
|
|
||||||
return DefaultClient.fetch<SystemTask>(["admin", "system_tasks", task_name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<SystemTask[]> {
|
|
||||||
return DefaultClient.fetch<SystemTask[]>(["admin", "system_tasks"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static retry(task_name: string): string {
|
|
||||||
return DefaultClient.makeUrl(["admin", "system_tasks", task_name, "retry"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { AKResponse, DefaultClient, QueryArguments } from "./Client";
|
|
||||||
import { User } from "./Users";
|
|
||||||
|
|
||||||
export enum TokenIntent {
|
|
||||||
INTENT_VERIFICATION = "verification",
|
|
||||||
INTENT_API = "api",
|
|
||||||
INTENT_RECOVERY = "recovery",
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Token {
|
|
||||||
|
|
||||||
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 userUrl(rest: string): string {
|
|
||||||
return `/-/user/tokens/${rest}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static getKey(identifier: string): Promise<string> {
|
|
||||||
return DefaultClient.fetch<{ key: string }>(["core", "tokens", identifier, "view_key"]).then(
|
|
||||||
(r) => r.key
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,45 +1,11 @@
|
||||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
|
import { CoreApi } from "./apis";
|
||||||
|
import { DEFAULT_CONFIG } from "./Config";
|
||||||
|
import { User } from "./models";
|
||||||
|
|
||||||
let _globalMePromise: Promise<User>;
|
let _globalMePromise: Promise<User>;
|
||||||
|
export function me(): Promise<User> {
|
||||||
export class User {
|
|
||||||
pk: number;
|
|
||||||
username: string;
|
|
||||||
name: string;
|
|
||||||
is_superuser: boolean;
|
|
||||||
email: boolean;
|
|
||||||
avatar: string;
|
|
||||||
is_active: boolean;
|
|
||||||
last_login: number;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(pk: string): Promise<User> {
|
|
||||||
return DefaultClient.fetch<User>(["core", "users", pk]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static list(filter?: QueryArguments): Promise<AKResponse<User>> {
|
|
||||||
return DefaultClient.fetch<AKResponse<User>>(["core", "users"], filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adminUrl(rest: string): string {
|
|
||||||
return `/administration/users/${rest}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static me(): Promise<User> {
|
|
||||||
if (!_globalMePromise) {
|
if (!_globalMePromise) {
|
||||||
_globalMePromise = DefaultClient.fetch<User>(["core", "users", "me"]);
|
_globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe({});
|
||||||
}
|
}
|
||||||
return _globalMePromise;
|
return _globalMePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
static count(): Promise<number> {
|
|
||||||
return DefaultClient.fetch<AKResponse<User>>(["core", "users"], {
|
|
||||||
"page_size": 1
|
|
||||||
}).then(r => {
|
|
||||||
return r.pagination.count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { DefaultClient } from "./Client";
|
|
||||||
|
|
||||||
export class Version {
|
|
||||||
|
|
||||||
version_current: string;
|
|
||||||
version_latest: string;
|
|
||||||
outdated: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(): Promise<Version> {
|
|
||||||
return DefaultClient.fetch<Version>(["admin", "version"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
97
web/src/api/legacy.ts
Normal file
97
web/src/api/legacy.ts
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
export class AdminURLManager {
|
||||||
|
|
||||||
|
static applications(rest: string): string {
|
||||||
|
return `/administration/applications/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static cryptoCertificates(rest: string): string {
|
||||||
|
return `/administration/crypto/certificates/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static policies(rest: string): string {
|
||||||
|
return `/administration/policies/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static policyBindings(rest: string): string {
|
||||||
|
return `/administration/policies/bindings/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static providers(rest: string): string {
|
||||||
|
return `/administration/providers/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static propertyMappings(rest: string): string {
|
||||||
|
return `/administration/property-mappings/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static outposts(rest: string): string {
|
||||||
|
return `/administration/outposts/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static outpostServiceConnections(rest: string): string {
|
||||||
|
return `/administration/outpost_service_connections/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static flows(rest: string): string {
|
||||||
|
return `/administration/flows/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static stages(rest: string): string {
|
||||||
|
return `/administration/stages/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static stagePrompts(rest: string): string {
|
||||||
|
return `/administration/stages_prompts/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static stageInvitations(rest: string): string {
|
||||||
|
return `/administration/stages/invitations/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static stageBindings(rest: string): string {
|
||||||
|
return `/administration/stages/bindings/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sources(rest: string): string {
|
||||||
|
return `/administration/sources/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tokens(rest: string): string {
|
||||||
|
return `/administration/tokens/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static eventRules(rest: string): string {
|
||||||
|
return `/administration/events/rules/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static eventTransports(rest: string): string {
|
||||||
|
return `/administration/events/transports/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static users(rest: string): string {
|
||||||
|
return `/administration/users/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static groups(rest: string): string {
|
||||||
|
return `/administration/groups/${rest}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserURLManager {
|
||||||
|
|
||||||
|
static tokens(rest: string): string {
|
||||||
|
return `/-/user/tokens/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AppURLManager {
|
||||||
|
|
||||||
|
static sourceSAML(slug: string, rest: string): string {
|
||||||
|
return `/source/saml/${slug}/${rest}`;
|
||||||
|
}
|
||||||
|
static providerSAML(rest: string): string {
|
||||||
|
return `/application/saml/${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
import { DefaultClient } from "../Client";
|
|
||||||
import { Provider } from "../Providers";
|
|
||||||
|
|
||||||
export interface OAuth2SetupURLs {
|
|
||||||
|
|
||||||
issuer?: string;
|
|
||||||
authorize: string;
|
|
||||||
token: string;
|
|
||||||
user_info: string;
|
|
||||||
provider_info?: string;
|
|
||||||
logout?: string;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OAuth2Provider extends Provider {
|
|
||||||
client_type: string
|
|
||||||
client_id: string;
|
|
||||||
client_secret: string;
|
|
||||||
token_validity: string;
|
|
||||||
include_claims_in_id_token: boolean;
|
|
||||||
jwt_alg: string;
|
|
||||||
rsa_key: string;
|
|
||||||
redirect_uris: string;
|
|
||||||
sub_mode: string;
|
|
||||||
issuer_mode: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(id: number): Promise<OAuth2Provider> {
|
|
||||||
return DefaultClient.fetch<OAuth2Provider>(["providers", "oauth2", id.toString()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getLaunchURls(id: number): Promise<OAuth2SetupURLs> {
|
|
||||||
return DefaultClient.fetch(["providers", "oauth2", id.toString(), "setup_urls"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static appUrl(rest: string): string {
|
|
||||||
return `/application/oauth2/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { DefaultClient } from "../Client";
|
|
||||||
import { Provider } from "../Providers";
|
|
||||||
|
|
||||||
export class ProxyProvider extends Provider {
|
|
||||||
internal_host: string;
|
|
||||||
external_host: string;
|
|
||||||
internal_host_ssl_validation: boolean
|
|
||||||
certificate?: string;
|
|
||||||
skip_path_regex: string;
|
|
||||||
basic_auth_enabled: boolean;
|
|
||||||
basic_auth_password_attribute: string;
|
|
||||||
basic_auth_user_attribute: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(id: number): Promise<ProxyProvider> {
|
|
||||||
return DefaultClient.fetch<ProxyProvider>(["providers", "proxy", id.toString()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getMetadata(id: number): Promise<{ metadata: string }> {
|
|
||||||
return DefaultClient.fetch(["providers", "proxy", id.toString(), "metadata"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static appUrl(rest: string): string {
|
|
||||||
return `/application/proxy/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { DefaultClient } from "../Client";
|
|
||||||
import { Provider } from "../Providers";
|
|
||||||
|
|
||||||
export class SAMLProvider extends Provider {
|
|
||||||
acs_url: string;
|
|
||||||
audience: string;
|
|
||||||
issuer: string;
|
|
||||||
assertion_valid_not_before: string;
|
|
||||||
assertion_valid_not_on_or_after: string;
|
|
||||||
session_valid_not_on_or_after: string;
|
|
||||||
name_id_mapping?: string;
|
|
||||||
digest_algorithm: string;
|
|
||||||
signature_algorithm: string;
|
|
||||||
signing_kp?: string;
|
|
||||||
verification_kp?: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(id: number): Promise<SAMLProvider> {
|
|
||||||
return DefaultClient.fetch<SAMLProvider>(["providers", "saml", id.toString()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getMetadata(id: number): Promise<{ metadata: string }> {
|
|
||||||
return DefaultClient.fetch(["providers", "saml", id.toString(), "metadata"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static appUrl(rest: string): string {
|
|
||||||
return `/application/saml/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { DefaultClient } from "../Client";
|
|
||||||
import { Source } from "../Sources";
|
|
||||||
|
|
||||||
export class LDAPSource extends Source {
|
|
||||||
server_uri: string;
|
|
||||||
bind_cn: string;
|
|
||||||
start_tls: boolean
|
|
||||||
base_dn: string;
|
|
||||||
additional_user_dn: string;
|
|
||||||
additional_group_dn: string;
|
|
||||||
user_object_filter: string;
|
|
||||||
group_object_filter: string;
|
|
||||||
group_membership_field: string;
|
|
||||||
object_uniqueness_field: string;
|
|
||||||
sync_users: boolean;
|
|
||||||
sync_users_password: boolean;
|
|
||||||
sync_groups: boolean;
|
|
||||||
sync_parent_group?: string;
|
|
||||||
property_mappings: string[];
|
|
||||||
property_mappings_group: string[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<LDAPSource> {
|
|
||||||
return DefaultClient.fetch<LDAPSource>(["sources", "ldap", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static syncStatus(slug: string): Promise<{ last_sync?: number }> {
|
|
||||||
return DefaultClient.fetch(["sources", "ldap", slug, "sync_status"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { DefaultClient } from "../Client";
|
|
||||||
import { Source } from "../Sources";
|
|
||||||
|
|
||||||
export class OAuthSource extends Source {
|
|
||||||
provider_type: string;
|
|
||||||
request_token_url: string;
|
|
||||||
authorization_url: string;
|
|
||||||
access_token_url: string;
|
|
||||||
profile_url: string;
|
|
||||||
consumer_key: string;
|
|
||||||
callback_url: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<OAuthSource> {
|
|
||||||
return DefaultClient.fetch<OAuthSource>(["sources", "oauth", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { DefaultClient } from "../Client";
|
|
||||||
import { Source } from "../Sources";
|
|
||||||
|
|
||||||
export class SAMLSource extends Source {
|
|
||||||
issuer: string;
|
|
||||||
sso_url: string;
|
|
||||||
slo_url: string;
|
|
||||||
allow_idp_initiated: boolean;
|
|
||||||
name_id_policy: string;
|
|
||||||
binding_type: string
|
|
||||||
signing_kp?: string;
|
|
||||||
digest_algorithm: string;
|
|
||||||
signature_algorithm: string;
|
|
||||||
temporary_user_delete_after: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(slug: string): Promise<SAMLSource> {
|
|
||||||
return DefaultClient.fetch<SAMLSource>(["sources", "saml", slug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getMetadata(slug: string): Promise<{ metadata: string }> {
|
|
||||||
return DefaultClient.fetch(["sources", "saml", slug, "metadata"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static appUrl(slug: string, rest: string): string {
|
|
||||||
return `/source/saml/${slug}/${rest}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -157,7 +157,7 @@ ak-message {
|
||||||
color: var(--ak-dark-foreground) !important;
|
color: var(--ak-dark-foreground) !important;
|
||||||
}
|
}
|
||||||
/* tabs, vertical */
|
/* tabs, vertical */
|
||||||
.pf-c-tabs__link {
|
.pf-c-tabs.pf-m-vertical .pf-c-tabs__link {
|
||||||
background-color: var(--ak-dark-background-light);
|
background-color: var(--ak-dark-background-light);
|
||||||
}
|
}
|
||||||
/* table, on mobile */
|
/* table, on mobile */
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
|
||||||
import Chart from "chart.js";
|
|
||||||
import { DefaultClient } from "../api/Client";
|
|
||||||
|
|
||||||
interface TickValue {
|
|
||||||
value: number;
|
|
||||||
major: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginMetrics {
|
|
||||||
logins_failed_per_1h: { x: number, y: number }[];
|
|
||||||
logins_per_1h: { x: number, y: number }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ak-admin-logins-chart")
|
|
||||||
export class AdminLoginsChart extends LitElement {
|
|
||||||
@property({type: Array})
|
|
||||||
url: string[] = [];
|
|
||||||
|
|
||||||
chart?: Chart;
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [css`
|
|
||||||
:host {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
min-height: 25rem;
|
|
||||||
}
|
|
||||||
canvas {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
`];
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
if (this.chart) {
|
|
||||||
this.chart.resize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated(): void {
|
|
||||||
DefaultClient.fetch<LoginMetrics>(this.url)
|
|
||||||
.then((r) => {
|
|
||||||
const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas");
|
|
||||||
if (!canvas) {
|
|
||||||
console.warn("Failed to get canvas element");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (!ctx) {
|
|
||||||
console.warn("failed to get 2d context");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.chart = new Chart(ctx, {
|
|
||||||
type: "bar",
|
|
||||||
data: {
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "Failed Logins",
|
|
||||||
backgroundColor: "rgba(201, 25, 11, .5)",
|
|
||||||
spanGaps: true,
|
|
||||||
data: r.logins_failed_per_1h,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Successful Logins",
|
|
||||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
|
||||||
spanGaps: true,
|
|
||||||
data: r.logins_per_1h,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
spanGaps: true,
|
|
||||||
scales: {
|
|
||||||
xAxes: [
|
|
||||||
{
|
|
||||||
stacked: true,
|
|
||||||
gridLines: {
|
|
||||||
color: "rgba(0, 0, 0, 0)",
|
|
||||||
},
|
|
||||||
type: "time",
|
|
||||||
offset: true,
|
|
||||||
ticks: {
|
|
||||||
callback: function (value, index: number, values) {
|
|
||||||
const valueStamp = <TickValue>(<unknown>values[index]);
|
|
||||||
const delta = Date.now() - valueStamp.value;
|
|
||||||
const ago = Math.round(delta / 1000 / 3600);
|
|
||||||
return `${ago} Hours ago`;
|
|
||||||
},
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
stacked: true,
|
|
||||||
gridLines: {
|
|
||||||
color: "rgba(0, 0, 0, 0)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): TemplateResult {
|
|
||||||
return html`<canvas></canvas>`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { getCookie } from "../../utils";
|
|
||||||
import { customElement, property } from "lit-element";
|
import { customElement, property } from "lit-element";
|
||||||
import { ERROR_CLASS, SUCCESS_CLASS } from "../../constants";
|
import { ERROR_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||||
import { SpinnerButton } from "./SpinnerButton";
|
import { SpinnerButton } from "./SpinnerButton";
|
||||||
|
@ -12,26 +11,16 @@ export class ActionButton extends SpinnerButton {
|
||||||
@property()
|
@property()
|
||||||
method = "POST";
|
method = "POST";
|
||||||
|
|
||||||
|
@property({attribute: false})
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
apiRequest: () => Promise<any> = () => { throw new Error(); };
|
||||||
|
|
||||||
callAction(): void {
|
callAction(): void {
|
||||||
if (this.isRunning === true) {
|
if (this.isRunning === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setLoading();
|
this.setLoading();
|
||||||
const csrftoken = getCookie("authentik_csrf");
|
this.apiRequest().then(() => {
|
||||||
const request = new Request(this.url, {
|
|
||||||
headers: { "X-CSRFToken": csrftoken },
|
|
||||||
});
|
|
||||||
fetch(request, {
|
|
||||||
method: this.method,
|
|
||||||
mode: "same-origin",
|
|
||||||
})
|
|
||||||
.then((r) => {
|
|
||||||
if (!r.ok) {
|
|
||||||
throw r;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.setDone(SUCCESS_CLASS);
|
this.setDone(SUCCESS_CLASS);
|
||||||
})
|
})
|
||||||
.catch((e: Error | Response) => {
|
.catch((e: Error | Response) => {
|
||||||
|
|
|
@ -3,9 +3,10 @@ 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 { Token } from "../../api/Tokens";
|
import { CoreApi } from "../../api";
|
||||||
import { ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
import { ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||||
import { ColorStyles } from "../../common/styles";
|
import { ColorStyles } from "../../common/styles";
|
||||||
|
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||||
|
|
||||||
@customElement("ak-token-copy-button")
|
@customElement("ak-token-copy-button")
|
||||||
export class TokenCopyButton extends LitElement {
|
export class TokenCopyButton extends LitElement {
|
||||||
|
@ -36,8 +37,14 @@ export class TokenCopyButton extends LitElement {
|
||||||
}, 1500);
|
}, 1500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Token.getKey(this.identifier).then((token) => {
|
new CoreApi(DEFAULT_CONFIG).coreTokensViewKey({
|
||||||
navigator.clipboard.writeText(token).then(() => {
|
identifier: this.identifier
|
||||||
|
}).then((token) => {
|
||||||
|
if (!token.key) {
|
||||||
|
this.buttonClass = ERROR_CLASS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(token.key).then(() => {
|
||||||
this.buttonClass = SUCCESS_CLASS;
|
this.buttonClass = SUCCESS_CLASS;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.buttonClass = PRIMARY_CLASS;
|
this.buttonClass = PRIMARY_CLASS;
|
||||||
|
|
41
web/src/elements/charts/AdminLoginsChart.ts
Normal file
41
web/src/elements/charts/AdminLoginsChart.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
import Chart from "chart.js";
|
||||||
|
import { AdminApi, LoginMetrics } from "../../api";
|
||||||
|
import { AKChart } from "./Chart";
|
||||||
|
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||||
|
|
||||||
|
@customElement("ak-charts-admin-login")
|
||||||
|
export class AdminLoginsChart extends AKChart<LoginMetrics> {
|
||||||
|
|
||||||
|
apiRequest(): Promise<LoginMetrics> {
|
||||||
|
return new AdminApi(DEFAULT_CONFIG).adminMetricsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDatasets(data: LoginMetrics): Chart.ChartDataSets[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: "Failed Logins",
|
||||||
|
backgroundColor: "rgba(201, 25, 11, .5)",
|
||||||
|
spanGaps: true,
|
||||||
|
data: data.loginsFailedPer1h?.map((cord) => {
|
||||||
|
return {
|
||||||
|
x: cord.xCord,
|
||||||
|
y: cord.yCord,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Successful Logins",
|
||||||
|
backgroundColor: "rgba(189, 229, 184, .5)",
|
||||||
|
spanGaps: true,
|
||||||
|
data: data.loginsPer1h?.map((cord) => {
|
||||||
|
return {
|
||||||
|
x: cord.xCord,
|
||||||
|
y: cord.yCord,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
web/src/elements/charts/ApplicationAuthorizeChart.ts
Normal file
32
web/src/elements/charts/ApplicationAuthorizeChart.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { customElement, property } from "lit-element";
|
||||||
|
import { Coordinate, CoreApi } from "../../api";
|
||||||
|
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||||
|
import { AKChart } from "./Chart";
|
||||||
|
|
||||||
|
@customElement("ak-charts-application-authorize")
|
||||||
|
export class ApplicationAuthorizeChart extends AKChart<Coordinate[]> {
|
||||||
|
|
||||||
|
@property()
|
||||||
|
applicationSlug!: string;
|
||||||
|
|
||||||
|
apiRequest(): Promise<Coordinate[]> {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreApplicationsMetrics({ slug: this.applicationSlug });
|
||||||
|
}
|
||||||
|
|
||||||
|
getDatasets(data: Coordinate[]): Chart.ChartDataSets[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: "Authorizations",
|
||||||
|
backgroundColor: "rgba(189, 229, 184, .5)",
|
||||||
|
spanGaps: true,
|
||||||
|
data: data.map((cord) => {
|
||||||
|
return {
|
||||||
|
x: cord.xCord,
|
||||||
|
y: cord.yCord,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
103
web/src/elements/charts/Chart.ts
Normal file
103
web/src/elements/charts/Chart.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { css, CSSResult, html, LitElement, TemplateResult } from "lit-element";
|
||||||
|
import Chart from "chart.js";
|
||||||
|
|
||||||
|
interface TickValue {
|
||||||
|
value: number;
|
||||||
|
major: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AKChart<T> extends LitElement {
|
||||||
|
|
||||||
|
abstract apiRequest(): Promise<T>;
|
||||||
|
abstract getDatasets(data: T): Chart.ChartDataSets[];
|
||||||
|
|
||||||
|
chart?: Chart;
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [css`
|
||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
min-height: 25rem;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
`];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
if (this.chart) {
|
||||||
|
this.chart.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
configureChart(data: T, ctx: CanvasRenderingContext2D): Chart {
|
||||||
|
return new Chart(ctx, {
|
||||||
|
type: "bar",
|
||||||
|
data: {
|
||||||
|
datasets: this.getDatasets(data),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
spanGaps: true,
|
||||||
|
scales: {
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
stacked: true,
|
||||||
|
gridLines: {
|
||||||
|
color: "rgba(0, 0, 0, 0)",
|
||||||
|
},
|
||||||
|
type: "time",
|
||||||
|
offset: true,
|
||||||
|
ticks: {
|
||||||
|
callback: function (value, index: number, values) {
|
||||||
|
const valueStamp = <TickValue>(<unknown>values[index]);
|
||||||
|
const delta = Date.now() - valueStamp.value;
|
||||||
|
const ago = Math.round(delta / 1000 / 3600);
|
||||||
|
return `${ago} Hours ago`;
|
||||||
|
},
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
stacked: true,
|
||||||
|
gridLines: {
|
||||||
|
color: "rgba(0, 0, 0, 0)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated(): void {
|
||||||
|
this.apiRequest().then((r) => {
|
||||||
|
const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas");
|
||||||
|
if (!canvas) {
|
||||||
|
console.warn("Failed to get canvas element");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
console.warn("failed to get 2d context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.chart = this.configureChart(r, ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
return html`<canvas></canvas>`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ export class MessageContainer extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(): void {
|
connect(): void {
|
||||||
|
if (navigator.webdriver) return;
|
||||||
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
|
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
|
||||||
window.location.host
|
window.location.host
|
||||||
}/ws/client/`;
|
}/ws/client/`;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||||
|
import { EventsApi, Notification } from "../../api";
|
||||||
import { AKResponse } from "../../api/Client";
|
import { AKResponse } from "../../api/Client";
|
||||||
import { Notification } from "../../api/EventNotification";
|
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||||
import { COMMON_STYLES } from "../../common/styles";
|
import { COMMON_STYLES } from "../../common/styles";
|
||||||
|
|
||||||
@customElement("ak-notification-drawer")
|
@customElement("ak-notification-drawer")
|
||||||
|
@ -30,9 +31,9 @@ export class NotificationDrawer extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated(): void {
|
firstUpdated(): void {
|
||||||
Notification.list({
|
new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
|
||||||
seen: false,
|
seen: "false",
|
||||||
ordering: "-created"
|
ordering: "-created",
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
this.notifications = r;
|
this.notifications = r;
|
||||||
this.unread = r.results.length;
|
this.unread = r.results.length;
|
||||||
|
@ -40,7 +41,6 @@ export class NotificationDrawer extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem(item: Notification): TemplateResult {
|
renderItem(item: Notification): TemplateResult {
|
||||||
const created = new Date(parseInt(item.created, 10) * 1000);
|
|
||||||
let level = "";
|
let level = "";
|
||||||
switch (item.severity) {
|
switch (item.severity) {
|
||||||
case "notice":
|
case "notice":
|
||||||
|
@ -66,15 +66,18 @@ export class NotificationDrawer extends LitElement {
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-notification-drawer__list-item-action">
|
<div class="pf-c-notification-drawer__list-item-action">
|
||||||
<button class="pf-c-dropdown__toggle pf-m-plain" type="button" @click=${() => {
|
<button class="pf-c-dropdown__toggle pf-m-plain" type="button" @click=${() => {
|
||||||
Notification.markSeen(item.pk).then(() => {
|
new EventsApi(DEFAULT_CONFIG).eventsNotificationsPartialUpdate({
|
||||||
this.firstUpdated();
|
uuid: item.pk || "",
|
||||||
|
data: {
|
||||||
|
seen: true,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}}>
|
}}>
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="pf-c-notification-drawer__list-item-description">${item.body}</p>
|
<p class="pf-c-notification-drawer__list-item-description">${item.body}</p>
|
||||||
<small class="pf-c-notification-drawer__list-item-timestamp">${created.toLocaleString()}</small>
|
<small class="pf-c-notification-drawer__list-item-timestamp">${item.created?.toLocaleString()}</small>
|
||||||
</li>`;
|
</li>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,16 @@ import { gettext } from "django";
|
||||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { AKResponse } from "../../api/Client";
|
import { AKResponse } from "../../api/Client";
|
||||||
import { Table, TableColumn } from "../../elements/table/Table";
|
import { Table, TableColumn } from "../../elements/table/Table";
|
||||||
import { PolicyBinding } from "../../api/PolicyBindings";
|
import { PoliciesApi, PolicyBinding } from "../../api";
|
||||||
|
|
||||||
import "../../elements/Tabs";
|
import "../../elements/Tabs";
|
||||||
import "../../elements/AdminLoginsChart";
|
|
||||||
import "../../elements/buttons/ModalButton";
|
import "../../elements/buttons/ModalButton";
|
||||||
import "../../elements/buttons/SpinnerButton";
|
import "../../elements/buttons/SpinnerButton";
|
||||||
import "../../elements/buttons/Dropdown";
|
import "../../elements/buttons/Dropdown";
|
||||||
import { Policy } from "../../api/Policies";
|
|
||||||
import { until } from "lit-html/directives/until";
|
import { until } from "lit-html/directives/until";
|
||||||
import { PAGE_SIZE } from "../../constants";
|
import { PAGE_SIZE } from "../../constants";
|
||||||
|
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||||
|
import { AdminURLManager } from "../../api/legacy";
|
||||||
|
|
||||||
@customElement("ak-bound-policies-list")
|
@customElement("ak-bound-policies-list")
|
||||||
export class BoundPoliciesList extends Table<PolicyBinding> {
|
export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||||
|
@ -19,11 +19,11 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||||
target?: string;
|
target?: string;
|
||||||
|
|
||||||
apiEndpoint(page: number): Promise<AKResponse<PolicyBinding>> {
|
apiEndpoint(page: number): Promise<AKResponse<PolicyBinding>> {
|
||||||
return PolicyBinding.list({
|
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
||||||
target: this.target || "",
|
target: this.target || "",
|
||||||
ordering: "order",
|
ordering: "order",
|
||||||
page: page,
|
page: page,
|
||||||
page_size: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +56,13 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||||
html`${item.order}`,
|
html`${item.order}`,
|
||||||
html`${item.timeout}`,
|
html`${item.timeout}`,
|
||||||
html`
|
html`
|
||||||
<ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/update/`)}">
|
<ak-modal-button href="${AdminURLManager.policyBindings(`${item.pk}/update/`)}">
|
||||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||||
${gettext("Edit")}
|
${gettext("Edit")}
|
||||||
</ak-spinner-button>
|
</ak-spinner-button>
|
||||||
<div slot="modal"></div>
|
<div slot="modal"></div>
|
||||||
</ak-modal-button>
|
</ak-modal-button>
|
||||||
<ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/delete/`)}">
|
<ak-modal-button href="${AdminURLManager.policyBindings(`${item.pk}/delete/`)}">
|
||||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||||
${gettext("Delete")}
|
${gettext("Delete")}
|
||||||
</ak-spinner-button>
|
</ak-spinner-button>
|
||||||
|
@ -78,7 +78,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||||
${gettext("No policies are currently bound to this object.")}
|
${gettext("No policies are currently bound to this object.")}
|
||||||
</div>
|
</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<ak-modal-button href=${PolicyBinding.adminUrl(`create/?target=${this.target}`)}>
|
<ak-modal-button href=${AdminURLManager.policyBindings(`create/?target=${this.target}`)}>
|
||||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||||
${gettext("Bind Policy")}
|
${gettext("Bind Policy")}
|
||||||
</ak-spinner-button>
|
</ak-spinner-button>
|
||||||
|
@ -96,7 +96,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||||
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
|
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="pf-c-dropdown__menu" hidden>
|
<ul class="pf-c-dropdown__menu" hidden>
|
||||||
${until(Policy.getTypes().then((types) => {
|
${until(new PoliciesApi(DEFAULT_CONFIG).policiesAllTypes({}).then((types) => {
|
||||||
return types.map((type) => {
|
return types.map((type) => {
|
||||||
return html`<li>
|
return html`<li>
|
||||||
<ak-modal-button href="${type.link}">
|
<ak-modal-button href="${type.link}">
|
||||||
|
@ -110,7 +110,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||||
}), html`<ak-spinner></ak-spinner>`)}
|
}), html`<ak-spinner></ak-spinner>`)}
|
||||||
</ul>
|
</ul>
|
||||||
</ak-dropdown>
|
</ak-dropdown>
|
||||||
<ak-modal-button href=${PolicyBinding.adminUrl(`create/?target=${this.target}`)}>
|
<ak-modal-button href=${AdminURLManager.policyBindings(`create/?target=${this.target}`)}>
|
||||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||||
${gettext("Bind Policy")}
|
${gettext("Bind Policy")}
|
||||||
</ak-spinner-button>
|
</ak-spinner-button>
|
||||||
|
|
|
@ -3,15 +3,17 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
|
||||||
import PageStyle from "@patternfly/patternfly/components/Page/page.css";
|
import PageStyle from "@patternfly/patternfly/components/Page/page.css";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
|
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
|
||||||
import { Config } from "../../api/Config";
|
import { configureSentry } from "../../api/Config";
|
||||||
|
import { Config } from "../../api";
|
||||||
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
|
|
||||||
export const DefaultConfig: Config = {
|
export const DefaultConfig: Config = {
|
||||||
branding_logo: " /static/dist/assets/icons/icon_left_brand.svg",
|
brandingLogo: " /static/dist/assets/icons/icon_left_brand.svg",
|
||||||
branding_title: "authentik",
|
brandingTitle: "authentik",
|
||||||
|
|
||||||
error_reporting_enabled: false,
|
errorReportingEnabled: false,
|
||||||
error_reporting_environment: "",
|
errorReportingEnvironment: "",
|
||||||
error_reporting_send_pii: false,
|
errorReportingSendPii: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ak-sidebar-brand")
|
@customElement("ak-sidebar-brand")
|
||||||
|
@ -40,13 +42,13 @@ export class SidebarBrand extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated(): void {
|
firstUpdated(): void {
|
||||||
Config.get().then((c) => (this.config = c));
|
configureSentry().then((c) => {this.config = c;});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html` <a href="#/" class="pf-c-page__header-brand-link">
|
return html` <a href="#/" class="pf-c-page__header-brand-link">
|
||||||
<div class="pf-c-brand ak-brand">
|
<div class="pf-c-brand ak-brand">
|
||||||
<img src="${this.config.branding_logo}" alt="authentik icon" loading="lazy" />
|
<img src="${ifDefined(this.config.brandingLogo)}" alt="authentik icon" loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import NavStyle from "@patternfly/patternfly/components/Nav/nav.css";
|
||||||
import fa from "@fortawesome/fontawesome-free/css/all.css";
|
import fa from "@fortawesome/fontawesome-free/css/all.css";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import AvatarStyle from "@patternfly/patternfly/components/Avatar/avatar.css";
|
import AvatarStyle from "@patternfly/patternfly/components/Avatar/avatar.css";
|
||||||
import { User } from "../../api/Users";
|
import { me } from "../../api/Users";
|
||||||
import { until } from "lit-html/directives/until";
|
import { until } from "lit-html/directives/until";
|
||||||
|
|
||||||
import "../notifications/NotificationTrigger";
|
import "../notifications/NotificationTrigger";
|
||||||
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
|
|
||||||
@customElement("ak-sidebar-user")
|
@customElement("ak-sidebar-user")
|
||||||
export class SidebarUser extends LitElement {
|
export class SidebarUser extends LitElement {
|
||||||
|
@ -37,8 +38,8 @@ export class SidebarUser extends LitElement {
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<a href="#/-/user/" class="pf-c-nav__link user-avatar" id="user-settings">
|
<a href="#/-/user/" class="pf-c-nav__link user-avatar" id="user-settings">
|
||||||
${until(User.me().then((u) => {
|
${until(me().then((u) => {
|
||||||
return html`<img class="pf-c-avatar" src="${u.avatar}" alt="" />`;
|
return html`<img class="pf-c-avatar" src="${ifDefined(u.avatar)}" alt="" />`;
|
||||||
}), html``)}
|
}), html``)}
|
||||||
</a>
|
</a>
|
||||||
<ak-notification-trigger class="pf-c-nav__link user-notifications">
|
<ak-notification-trigger class="pf-c-nav__link user-notifications">
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { COMMON_STYLES } from "../../common/styles";
|
||||||
|
|
||||||
import "./TablePagination";
|
import "./TablePagination";
|
||||||
import "../EmptyState";
|
import "../EmptyState";
|
||||||
|
import "../Spinner";
|
||||||
|
|
||||||
export class TableColumn {
|
export class TableColumn {
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||||
import { COMMON_STYLES } from "../../common/styles";
|
import { COMMON_STYLES } from "../../common/styles";
|
||||||
import { PBPagination } from "../../api/Client";
|
import { AKPagination } from "../../api/Client";
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
|
|
||||||
@customElement("ak-table-pagination")
|
@customElement("ak-table-pagination")
|
||||||
export class TablePagination extends LitElement {
|
export class TablePagination extends LitElement {
|
||||||
@property({attribute: false})
|
@property({attribute: false})
|
||||||
pages?: PBPagination;
|
pages?: AKPagination;
|
||||||
|
|
||||||
@property({attribute: false})
|
@property({attribute: false})
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
@ -22,8 +22,8 @@ export class TablePagination extends LitElement {
|
||||||
<div class="pf-c-options-menu">
|
<div class="pf-c-options-menu">
|
||||||
<div class="pf-c-options-menu__toggle pf-m-text pf-m-plain">
|
<div class="pf-c-options-menu__toggle pf-m-text pf-m-plain">
|
||||||
<span class="pf-c-options-menu__toggle-text">
|
<span class="pf-c-options-menu__toggle-text">
|
||||||
${this.pages?.start_index} -
|
${this.pages?.startIndex} -
|
||||||
${this.pages?.end_index} of
|
${this.pages?.endIndex} of
|
||||||
${this.pages?.count}
|
${this.pages?.count}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import "construct-style-sheets-polyfill";
|
import "construct-style-sheets-polyfill";
|
||||||
|
|
||||||
import "./pages/generic/FlowExecutor";
|
import "./flows/FlowExecutor";
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { LitElement, html, customElement, property, TemplateResult, CSSResult, css } from "lit-element";
|
import { LitElement, html, customElement, property, TemplateResult, CSSResult, css } from "lit-element";
|
||||||
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
||||||
import { getCookie } from "../../utils";
|
import "./stages/authenticator_static/AuthenticatorStaticStage";
|
||||||
import "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
import "./stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||||
import "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
import "./stages/authenticator_validate/AuthenticatorValidateStage";
|
||||||
import "../../elements/stages/authenticator_validate/AuthenticatorValidateStage";
|
import "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||||
import "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
import "./stages/autosubmit/AutosubmitStage";
|
||||||
import "../../elements/stages/autosubmit/AutosubmitStage";
|
import "./stages/captcha/CaptchaStage";
|
||||||
import "../../elements/stages/captcha/CaptchaStage";
|
import "./stages/consent/ConsentStage";
|
||||||
import "../../elements/stages/consent/ConsentStage";
|
import "./stages/email/EmailStage";
|
||||||
import "../../elements/stages/email/EmailStage";
|
import "./stages/identification/IdentificationStage";
|
||||||
import "../../elements/stages/identification/IdentificationStage";
|
import "./stages/password/PasswordStage";
|
||||||
import "../../elements/stages/password/PasswordStage";
|
import "./stages/prompt/PromptStage";
|
||||||
import "../../elements/stages/prompt/PromptStage";
|
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
|
||||||
import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows";
|
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
|
||||||
import { DefaultClient } from "../../api/Client";
|
import { PasswordChallenge } from "./stages/password/PasswordStage";
|
||||||
import { IdentificationChallenge } from "../../elements/stages/identification/IdentificationStage";
|
import { ConsentChallenge } from "./stages/consent/ConsentStage";
|
||||||
import { PasswordChallenge } from "../../elements/stages/password/PasswordStage";
|
import { EmailChallenge } from "./stages/email/EmailStage";
|
||||||
import { ConsentChallenge } from "../../elements/stages/consent/ConsentStage";
|
import { AutosubmitChallenge } from "./stages/autosubmit/AutosubmitStage";
|
||||||
import { EmailChallenge } from "../../elements/stages/email/EmailStage";
|
import { PromptChallenge } from "./stages/prompt/PromptStage";
|
||||||
import { AutosubmitChallenge } from "../../elements/stages/autosubmit/AutosubmitStage";
|
import { AuthenticatorTOTPChallenge } from "./stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||||
import { PromptChallenge } from "../../elements/stages/prompt/PromptStage";
|
import { AuthenticatorStaticChallenge } from "./stages/authenticator_static/AuthenticatorStaticStage";
|
||||||
import { AuthenticatorTOTPChallenge } from "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
import { AuthenticatorValidateStageChallenge } from "./stages/authenticator_validate/AuthenticatorValidateStage";
|
||||||
import { AuthenticatorStaticChallenge } from "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
import { WebAuthnAuthenticatorRegisterChallenge } from "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||||
import { AuthenticatorValidateStageChallenge } from "../../elements/stages/authenticator_validate/AuthenticatorValidateStage";
|
import { CaptchaChallenge } from "./stages/captcha/CaptchaStage";
|
||||||
import { WebAuthnAuthenticatorRegisterChallenge } from "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
import { COMMON_STYLES } from "../common/styles";
|
||||||
import { CaptchaChallenge } from "../../elements/stages/captcha/CaptchaStage";
|
import { SpinnerSize } from "../elements/Spinner";
|
||||||
import { COMMON_STYLES } from "../../common/styles";
|
import { StageHost } from "./stages/base";
|
||||||
import { SpinnerSize } from "../../elements/Spinner";
|
import { Challenge, ChallengeTypeEnum, FlowsApi } from "../api";
|
||||||
import { StageHost } from "../../elements/stages/base";
|
import { DEFAULT_CONFIG } from "../api/Config";
|
||||||
|
|
||||||
@customElement("ak-flow-executor")
|
@customElement("ak-flow-executor")
|
||||||
export class FlowExecutor extends LitElement implements StageHost {
|
export class FlowExecutor extends LitElement implements StageHost {
|
||||||
|
@ -68,37 +68,30 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(formData?: FormData): Promise<void> {
|
submit<T>(formData?: T): Promise<void> {
|
||||||
const csrftoken = getCookie("authentik_csrf");
|
|
||||||
const request = new Request(DefaultClient.makeUrl(["flows", "executor", this.flowSlug]), {
|
|
||||||
headers: {
|
|
||||||
"X-CSRFToken": csrftoken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
return fetch(request, {
|
return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolveRaw({
|
||||||
method: "POST",
|
flowSlug: this.flowSlug,
|
||||||
mode: "same-origin",
|
data: formData || {},
|
||||||
body: formData,
|
}).then((challengeRaw) => {
|
||||||
})
|
return challengeRaw.raw.json();
|
||||||
.then((response) => {
|
}).then((data) => {
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
this.challenge = data;
|
this.challenge = data;
|
||||||
})
|
}).catch((e) => {
|
||||||
.catch((e) => {
|
|
||||||
this.errorMessage(e);
|
this.errorMessage(e);
|
||||||
})
|
}).finally(() => {
|
||||||
.finally(() => {
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated(): void {
|
firstUpdated(): void {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
Flow.executor(this.flowSlug).then((challenge) => {
|
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGetRaw({
|
||||||
this.challenge = challenge;
|
flowSlug: this.flowSlug
|
||||||
|
}).then((challengeRaw) => {
|
||||||
|
return challengeRaw.raw.json();
|
||||||
|
}).then((challenge) => {
|
||||||
|
this.challenge = challenge as Challenge;
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
// Catch JSON or Update errors
|
// Catch JSON or Update errors
|
||||||
this.errorMessage(e);
|
this.errorMessage(e);
|
||||||
|
@ -109,7 +102,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||||
|
|
||||||
errorMessage(error: string): void {
|
errorMessage(error: string): void {
|
||||||
this.challenge = <ShellChallenge>{
|
this.challenge = <ShellChallenge>{
|
||||||
type: ChallengeTypes.shell,
|
type: ChallengeTypeEnum.Shell,
|
||||||
body: `<style>
|
body: `<style>
|
||||||
.ak-exception {
|
.ak-exception {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
@ -139,13 +132,13 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||||
return this.renderLoading();
|
return this.renderLoading();
|
||||||
}
|
}
|
||||||
switch (this.challenge.type) {
|
switch (this.challenge.type) {
|
||||||
case ChallengeTypes.redirect:
|
case ChallengeTypeEnum.Redirect:
|
||||||
console.debug(`authentik/flows: redirecting to ${(this.challenge as RedirectChallenge).to}`);
|
console.debug(`authentik/flows: redirecting to ${(this.challenge as RedirectChallenge).to}`);
|
||||||
window.location.assign((this.challenge as RedirectChallenge).to);
|
window.location.assign((this.challenge as RedirectChallenge).to);
|
||||||
return this.renderLoading();
|
return this.renderLoading();
|
||||||
case ChallengeTypes.shell:
|
case ChallengeTypeEnum.Shell:
|
||||||
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
|
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
|
||||||
case ChallengeTypes.native:
|
case ChallengeTypeEnum.Native:
|
||||||
switch (this.challenge.component) {
|
switch (this.challenge.component) {
|
||||||
case "ak-stage-identification":
|
case "ak-stage-identification":
|
||||||
return html`<ak-stage-identification .host=${this} .challenge=${this.challenge as IdentificationChallenge}></ak-stage-identification>`;
|
return html`<ak-stage-identification .host=${this} .challenge=${this.challenge as IdentificationChallenge}></ak-stage-identification>`;
|
|
@ -4,6 +4,7 @@ import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "../form";
|
import "../form";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
export interface AuthenticatorStaticChallenge extends WithUserInfoChallenge {
|
export interface AuthenticatorStaticChallenge extends WithUserInfoChallenge {
|
||||||
codes: number[];
|
codes: number[];
|
|
@ -5,7 +5,8 @@ import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "webcomponent-qr-code";
|
import "webcomponent-qr-code";
|
||||||
import "../form";
|
import "../form";
|
||||||
import { showMessage } from "../../messages/MessageContainer";
|
import { showMessage } from "../../../elements/messages/MessageContainer";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
export interface AuthenticatorTOTPChallenge extends WithUserInfoChallenge {
|
export interface AuthenticatorTOTPChallenge extends WithUserInfoChallenge {
|
||||||
config_url: string;
|
config_url: string;
|
|
@ -36,8 +36,8 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
|
||||||
@property({attribute: false})
|
@property({attribute: false})
|
||||||
selectedDeviceChallenge?: DeviceChallenge;
|
selectedDeviceChallenge?: DeviceChallenge;
|
||||||
|
|
||||||
submit(formData?: FormData): Promise<void> {
|
submit<T>(formData?: T): Promise<void> {
|
||||||
return this.host?.submit(formData) || Promise.resolve();
|
return this.host?.submit<T>(formData) || Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
|
@ -4,6 +4,7 @@ import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
|
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
|
||||||
import "../form";
|
import "../form";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
@customElement("ak-stage-authenticator-validate-code")
|
@customElement("ak-stage-authenticator-validate-code")
|
||||||
export class AuthenticatorValidateStageWebCode extends BaseStage {
|
export class AuthenticatorValidateStageWebCode extends BaseStage {
|
|
@ -1,7 +1,7 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { SpinnerSize } from "../../Spinner";
|
import { SpinnerSize } from "../../../elements/Spinner";
|
||||||
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
|
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
|
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
|
|
@ -1,7 +1,7 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { WithUserInfoChallenge } from "../../../api/Flows";
|
import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||||
import { SpinnerSize } from "../../Spinner";
|
import { SpinnerSize } from "../../../elements/Spinner";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
|
import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { CSSResult, customElement, html, property, TemplateResult } from "lit-el
|
||||||
import { WithUserInfoChallenge } from "../../../api/Flows";
|
import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "../../Spinner";
|
import "../../../elements/Spinner";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
export interface AutosubmitChallenge extends WithUserInfoChallenge {
|
export interface AutosubmitChallenge extends WithUserInfoChallenge {
|
||||||
url: string;
|
url: string;
|
|
@ -1,7 +1,7 @@
|
||||||
import { LitElement } from "lit-element";
|
import { LitElement } from "lit-element";
|
||||||
|
|
||||||
export interface StageHost {
|
export interface StageHost {
|
||||||
submit(formData?: FormData): Promise<void>;
|
submit<T>(formData?: T): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BaseStage extends LitElement {
|
export class BaseStage extends LitElement {
|
||||||
|
@ -10,8 +10,12 @@ export class BaseStage extends LitElement {
|
||||||
|
|
||||||
submitForm(e: Event): void {
|
submitForm(e: Event): void {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const object: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
} = {};
|
||||||
const form = new FormData(this.shadowRoot?.querySelector("form") || undefined);
|
const form = new FormData(this.shadowRoot?.querySelector("form") || undefined);
|
||||||
this.host?.submit(form);
|
form.forEach((value, key) => object[key] = value);
|
||||||
|
this.host?.submit(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,9 +2,10 @@ import { gettext } from "django";
|
||||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { WithUserInfoChallenge } from "../../../api/Flows";
|
import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { SpinnerSize } from "../../Spinner";
|
import { SpinnerSize } from "../../../elements/Spinner";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "../form";
|
import "../form";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
export interface CaptchaChallenge extends WithUserInfoChallenge {
|
export interface CaptchaChallenge extends WithUserInfoChallenge {
|
||||||
site_key: string;
|
site_key: string;
|
|
@ -3,6 +3,7 @@ import { CSSResult, customElement, html, property, TemplateResult } from "lit-el
|
||||||
import { WithUserInfoChallenge } from "../../../api/Flows";
|
import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
export interface Permission {
|
export interface Permission {
|
||||||
name: string;
|
name: string;
|
|
@ -1,10 +1,11 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { Challenge } from "../../../api/Flows";
|
import { Challenge } from "../../../api";
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
export type EmailChallenge = Challenge
|
export type EmailChallenge = Challenge;
|
||||||
|
|
||||||
@customElement("ak-stage-email")
|
@customElement("ak-stage-email")
|
||||||
export class EmailStage extends BaseStage {
|
export class EmailStage extends BaseStage {
|
|
@ -1,9 +1,10 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { Challenge } from "../../../api/Flows";
|
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "../form";
|
import "../form";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
import { Challenge } from "../../../api/Flows";
|
||||||
|
|
||||||
export interface IdentificationChallenge extends Challenge {
|
export interface IdentificationChallenge extends Challenge {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "../form";
|
import "../form";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
|
||||||
export interface PasswordChallenge extends WithUserInfoChallenge {
|
export interface PasswordChallenge extends WithUserInfoChallenge {
|
||||||
recovery_url?: string;
|
recovery_url?: string;
|
|
@ -1,10 +1,11 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
||||||
import { Challenge } from "../../../api/Flows";
|
|
||||||
import { COMMON_STYLES } from "../../../common/styles";
|
import { COMMON_STYLES } from "../../../common/styles";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "../form";
|
import "../form";
|
||||||
|
import "../../../elements/utils/LoadingState";
|
||||||
|
import { Challenge } from "../../../api/Flows";
|
||||||
|
|
||||||
export interface Prompt {
|
export interface Prompt {
|
||||||
field_key: string;
|
field_key: string;
|
|
@ -1,5 +1,5 @@
|
||||||
import { customElement } from "lit-element";
|
import { customElement } from "lit-element";
|
||||||
import { User } from "../api/Users";
|
import { me } from "../api/Users";
|
||||||
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
||||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "../elements/router/Route";
|
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "../elements/router/Route";
|
||||||
import { Interface } from "./Interface";
|
import { Interface } from "./Interface";
|
||||||
|
@ -10,7 +10,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
new SidebarItem("Overview", "/administration/overview"),
|
new SidebarItem("Overview", "/administration/overview"),
|
||||||
new SidebarItem("System Tasks", "/administration/system-tasks"),
|
new SidebarItem("System Tasks", "/administration/system-tasks"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return me().then(u => u.isSuperuser||false);
|
||||||
}),
|
}),
|
||||||
new SidebarItem("Events").children(
|
new SidebarItem("Events").children(
|
||||||
new SidebarItem("Log", "/events/log").activeWhen(
|
new SidebarItem("Log", "/events/log").activeWhen(
|
||||||
|
@ -19,7 +19,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
new SidebarItem("Notification Rules", "/events/rules"),
|
new SidebarItem("Notification Rules", "/events/rules"),
|
||||||
new SidebarItem("Notification Transports", "/events/transports"),
|
new SidebarItem("Notification Transports", "/events/transports"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return me().then(u => u.isSuperuser||false);
|
||||||
}),
|
}),
|
||||||
new SidebarItem("Resources").children(
|
new SidebarItem("Resources").children(
|
||||||
new SidebarItem("Applications", "/core/applications").activeWhen(
|
new SidebarItem("Applications", "/core/applications").activeWhen(
|
||||||
|
@ -34,13 +34,13 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
new SidebarItem("Outposts", "/outpost/outposts"),
|
new SidebarItem("Outposts", "/outpost/outposts"),
|
||||||
new SidebarItem("Outpost Service Connections", "/outpost/service-connections"),
|
new SidebarItem("Outpost Service Connections", "/outpost/service-connections"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return me().then(u => u.isSuperuser||false);
|
||||||
}),
|
}),
|
||||||
new SidebarItem("Customisation").children(
|
new SidebarItem("Customisation").children(
|
||||||
new SidebarItem("Policies", "/policy/policies"),
|
new SidebarItem("Policies", "/policy/policies"),
|
||||||
new SidebarItem("Property Mappings", "/core/property-mappings"),
|
new SidebarItem("Property Mappings", "/core/property-mappings"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return me().then(u => u.isSuperuser||false);
|
||||||
}),
|
}),
|
||||||
new SidebarItem("Flows").children(
|
new SidebarItem("Flows").children(
|
||||||
new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?<slug>${SLUG_REGEX})$`),
|
new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?<slug>${SLUG_REGEX})$`),
|
||||||
|
@ -48,7 +48,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
new SidebarItem("Prompts", "/flow/stages/prompts"),
|
new SidebarItem("Prompts", "/flow/stages/prompts"),
|
||||||
new SidebarItem("Invitations", "/flow/stages/invitations"),
|
new SidebarItem("Invitations", "/flow/stages/invitations"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return me().then(u => u.isSuperuser||false);
|
||||||
}),
|
}),
|
||||||
new SidebarItem("Identity & Cryptography").children(
|
new SidebarItem("Identity & Cryptography").children(
|
||||||
new SidebarItem("User", "/identity/users"),
|
new SidebarItem("User", "/identity/users"),
|
||||||
|
@ -56,7 +56,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
new SidebarItem("Certificates", "/crypto/certificates"),
|
new SidebarItem("Certificates", "/crypto/certificates"),
|
||||||
new SidebarItem("Tokens", "/core/tokens"),
|
new SidebarItem("Tokens", "/core/tokens"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return me().then(u => u.isSuperuser||false);
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,13 @@
|
||||||
import "construct-style-sheets-polyfill";
|
import "construct-style-sheets-polyfill";
|
||||||
|
|
||||||
|
// Elements that are used by SiteShell pages
|
||||||
|
// And can't dynamically be imported
|
||||||
import "./elements/buttons/ActionButton";
|
import "./elements/buttons/ActionButton";
|
||||||
import "./elements/buttons/Dropdown";
|
import "./elements/buttons/Dropdown";
|
||||||
import "./elements/buttons/ModalButton";
|
import "./elements/buttons/ModalButton";
|
||||||
import "./elements/buttons/SpinnerButton";
|
import "./elements/buttons/SpinnerButton";
|
||||||
import "./elements/buttons/TokenCopyButton";
|
|
||||||
|
|
||||||
import "./elements/sidebar/Sidebar";
|
|
||||||
import "./elements/sidebar/SidebarBrand";
|
|
||||||
import "./elements/sidebar/SidebarUser";
|
|
||||||
|
|
||||||
import "./elements/table/TablePagination";
|
|
||||||
|
|
||||||
import "./elements/AdminLoginsChart";
|
|
||||||
import "./elements/EmptyState";
|
|
||||||
import "./elements/cards/AggregateCard";
|
|
||||||
import "./elements/cards/AggregatePromiseCard";
|
|
||||||
import "./elements/CodeMirror";
|
import "./elements/CodeMirror";
|
||||||
import "./elements/messages/MessageContainer";
|
|
||||||
import "./elements/Spinner";
|
|
||||||
import "./elements/Tabs";
|
|
||||||
import "./elements/router/RouterOutlet";
|
|
||||||
|
|
||||||
import "./pages/generic/SiteShell";
|
|
||||||
|
|
||||||
import "./pages/admin-overview/AdminOverviewPage";
|
|
||||||
import "./pages/admin-overview/TopApplicationsTable";
|
|
||||||
import "./pages/applications/ApplicationListPage";
|
|
||||||
import "./pages/applications/ApplicationViewPage";
|
|
||||||
import "./pages/tokens/UserTokenList";
|
import "./pages/tokens/UserTokenList";
|
||||||
import "./pages/LibraryPage";
|
import "./pages/generic/SiteShell";
|
||||||
|
|
||||||
import "./interfaces/AdminInterface";
|
import "./interfaces/AdminInterface";
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||||
import { ifDefined } from "lit-html/directives/if-defined";
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
import { Application } from "../api/Applications";
|
import { Application, CoreApi } from "../api";
|
||||||
import { AKResponse } from "../api/Client";
|
import { AKResponse } from "../api/Client";
|
||||||
|
import { DEFAULT_CONFIG } from "../api/Config";
|
||||||
import { COMMON_STYLES } from "../common/styles";
|
import { COMMON_STYLES } from "../common/styles";
|
||||||
import { loading, truncate } from "../utils";
|
import { loading, truncate } from "../utils";
|
||||||
|
|
||||||
|
@ -31,19 +32,19 @@ export class LibraryApplication extends LitElement {
|
||||||
if (!this.application) {
|
if (!this.application) {
|
||||||
return html`<ak-spinner></ak-spinner>`;
|
return html`<ak-spinner></ak-spinner>`;
|
||||||
}
|
}
|
||||||
return html` <a href="${this.application.launch_url}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
return html` <a href="${ifDefined(this.application.launchUrl)}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
${this.application.meta_icon
|
${this.application.metaIcon
|
||||||
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.meta_icon)}" alt="Application Icon"/>`
|
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.metaIcon)}" alt="Application Icon"/>`
|
||||||
: html`<i class="pf-icon pf-icon-arrow"></i>`}
|
: html`<i class="pf-icon pf-icon-arrow"></i>`}
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__title">
|
<div class="pf-c-card__title">
|
||||||
<p id="card-1-check-label">${this.application.name}</p>
|
<p id="card-1-check-label">${this.application.name}</p>
|
||||||
<div class="pf-c-content">
|
<div class="pf-c-content">
|
||||||
<small>${this.application.meta_publisher}</small>
|
<small>${this.application.metaPublisher}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">${truncate(this.application.meta_description, 35)}</div>
|
<div class="pf-c-card__body">${truncate(this.application.metaDescription, 35)}</div>
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +65,9 @@ export class LibraryPage extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated(): void {
|
firstUpdated(): void {
|
||||||
Application.list().then((r) => (this.apps = r));
|
new CoreApi(DEFAULT_CONFIG).coreApplicationsList({}).then((apps) => {
|
||||||
|
this.apps = apps;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEmptyState(): TemplateResult {
|
renderEmptyState(): TemplateResult {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { gettext } from "django";
|
||||||
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
|
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||||
import { COMMON_STYLES } from "../../common/styles";
|
import { COMMON_STYLES } from "../../common/styles";
|
||||||
|
|
||||||
import "../../elements/AdminLoginsChart";
|
import "../../elements/charts/AdminLoginsChart";
|
||||||
import "../../elements/cards/AggregatePromiseCard";
|
import "../../elements/cards/AggregatePromiseCard";
|
||||||
import "./TopApplicationsTable";
|
import "./TopApplicationsTable";
|
||||||
import "./cards/AdminStatusCard";
|
import "./cards/AdminStatusCard";
|
||||||
|
@ -30,7 +30,7 @@ export class AdminOverviewPage extends LitElement {
|
||||||
<section class="pf-c-page__main-section">
|
<section class="pf-c-page__main-section">
|
||||||
<div class="pf-l-gallery pf-m-gutter">
|
<div class="pf-l-gallery pf-m-gutter">
|
||||||
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Logins over the last 24 hours" style="grid-column-end: span 3;grid-row-end: span 2;">
|
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Logins over the last 24 hours" style="grid-column-end: span 3;grid-row-end: span 2;">
|
||||||
<ak-admin-logins-chart .url="${["admin", "metrics"]}"></ak-admin-logins-chart>
|
<ak-charts-admin-login></ak-charts-admin-login>
|
||||||
</ak-aggregate-card>
|
</ak-aggregate-card>
|
||||||
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
|
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
|
||||||
<ak-top-applications-table></ak-top-applications-table>
|
<ak-top-applications-table></ak-top-applications-table>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue