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
|
||||
steps:
|
||||
- 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
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
|
|
@ -45,4 +45,5 @@ COPY ./lifecycle/ /lifecycle
|
|||
USER authentik
|
||||
STOPSIGNAL SIGINT
|
||||
ENV TMPDIR /dev/shm/
|
||||
ENV PYTHONUBUFFERED 1
|
||||
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.functions import ExtractHour
|
||||
from django.utils.timezone import now
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
|
||||
from rest_framework.fields import IntegerField, SerializerMethodField
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.request import Request
|
||||
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):
|
||||
results.append(
|
||||
{
|
||||
"x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
|
||||
"y": data[hour * -1],
|
||||
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple())
|
||||
* 1000,
|
||||
"y_cord": data[hour * -1],
|
||||
}
|
||||
)
|
||||
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"""
|
||||
|
||||
logins_per_1h = SerializerMethodField()
|
||||
logins_failed_per_1h = SerializerMethodField()
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
|
||||
def get_logins_per_1h(self, _):
|
||||
"""Get successful logins per hour for the last 24 hours"""
|
||||
return get_events_per_1h(action=EventAction.LOGIN)
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
|
||||
def get_logins_failed_per_1h(self, _):
|
||||
"""Get failed logins per hour for the last 24 hours"""
|
||||
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
|
||||
|
@ -70,8 +86,8 @@ class AdministrationMetricsViewSet(ViewSet):
|
|||
|
||||
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:
|
||||
"""Login Metrics per 1h"""
|
||||
serializer = AdministrationMetricsSerializer(True)
|
||||
serializer = LoginMetricsSerializer(True)
|
||||
return Response(serializer.data)
|
||||
|
|
|
@ -25,8 +25,8 @@ class TaskSerializer(Serializer):
|
|||
task_finish_timestamp = DateTimeField(source="finish_timestamp")
|
||||
|
||||
status = ChoiceField(
|
||||
source="result.status.value",
|
||||
choices=[(x.value, x.name) for x in TaskResultStatus],
|
||||
source="result.status.name",
|
||||
choices=[(x.name, x.name) for x in TaskResultStatus],
|
||||
)
|
||||
messages = ListField(source="result.messages")
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""api v2 urls"""
|
||||
from django.conf import settings
|
||||
from django.urls import path, re_path
|
||||
from drf_yasg2 import openapi
|
||||
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("policies/dummy", DummyPolicyViewSet)
|
||||
|
||||
api_urls = router.urls + [
|
||||
path(
|
||||
"flows/executor/<slug:flow_slug>/",
|
||||
FlowExecutorView.as_view(),
|
||||
name="flow-executor",
|
||||
),
|
||||
]
|
||||
|
||||
info = openapi.Info(
|
||||
title="authentik API",
|
||||
default_version="v2",
|
||||
|
@ -165,26 +174,22 @@ info = openapi.Info(
|
|||
),
|
||||
)
|
||||
SchemaView = get_schema_view(
|
||||
info,
|
||||
public=True,
|
||||
permission_classes=(AllowAny,),
|
||||
info, public=True, permission_classes=(AllowAny,), patterns=api_urls
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
urlpatterns = api_urls + [
|
||||
re_path(
|
||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||
SchemaView.without_ui(cache_timeout=0),
|
||||
name="schema-json",
|
||||
),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns = urlpatterns + [
|
||||
path(
|
||||
"swagger/",
|
||||
SchemaView.with_ui("swagger", cache_timeout=0),
|
||||
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.db.models import QuerySet
|
||||
from django.http.response import Http404
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
|
@ -13,7 +14,7 @@ from rest_framework.viewsets import ModelViewSet
|
|||
from rest_framework_guardian.filters import ObjectPermissionsFilter
|
||||
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.models import Application
|
||||
from authentik.events.models import EventAction
|
||||
|
@ -109,6 +110,7 @@ class ApplicationViewSet(ModelViewSet):
|
|||
serializer = self.get_serializer(allowed_applications, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(responses={200: CoordinateSerializer(many=True)})
|
||||
@action(detail=True)
|
||||
def metrics(self, request: Request, slug: str):
|
||||
"""Metrics for application logins"""
|
||||
|
|
|
@ -28,9 +28,9 @@ class MetaNameSerializer(Serializer):
|
|||
class TypeCreateSerializer(Serializer):
|
||||
"""Types of an object that can be created"""
|
||||
|
||||
name = CharField(read_only=True)
|
||||
description = CharField(read_only=True)
|
||||
link = CharField(read_only=True)
|
||||
name = CharField(required=True)
|
||||
description = CharField(required=True)
|
||||
link = CharField(required=True)
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
|
||||
{% block body %}
|
||||
<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>
|
||||
<ak-message-container></ak-message-container>
|
||||
<div class="pf-c-login">
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.contrib.auth.mixins import (
|
|||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http.response import HttpResponse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import UpdateView
|
||||
from django.views.generic.base import TemplateView
|
||||
|
@ -34,7 +35,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
|||
form_class = UserDetailForm
|
||||
|
||||
success_message = _("Successfully updated user.")
|
||||
success_url = "/"
|
||||
success_url = reverse_lazy("authentik_core:user-details")
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
|
|
@ -13,7 +13,7 @@ class NotificationSerializer(ModelSerializer):
|
|||
|
||||
body = ReadOnlyField()
|
||||
severity = ReadOnlyField()
|
||||
event = EventSerializer()
|
||||
event = EventSerializer(required=False)
|
||||
|
||||
class Meta:
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
"""NotificationTransport API Views"""
|
||||
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 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.response import Response
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.serializers import ModelSerializer, Serializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
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):
|
||||
"""NotificationTransport Viewset"""
|
||||
|
||||
queryset = NotificationTransport.objects.all()
|
||||
serializer_class = NotificationTransportSerializer
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={200: NotificationTransportTestSerializer(many=False)},
|
||||
request_body=no_body,
|
||||
)
|
||||
@action(detail=True, methods=["post"])
|
||||
# pylint: disable=invalid-name
|
||||
def test(self, request: Request, pk=None) -> Response:
|
||||
|
@ -61,6 +78,10 @@ class NotificationTransportViewSet(ModelViewSet):
|
|||
user=request.user,
|
||||
)
|
||||
try:
|
||||
return Response(transport.send(notification))
|
||||
response = NotificationTransportTestSerializer(
|
||||
data={"messages": transport.send(notification)}
|
||||
)
|
||||
response.is_valid()
|
||||
return Response(response.data)
|
||||
except NotificationTransportError as exc:
|
||||
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
|
||||
is currently active"""
|
||||
|
||||
type = ChoiceField(choices=list(ChallengeTypes))
|
||||
type = ChoiceField(
|
||||
choices=[(x.name, x.name) for x in ChallengeTypes],
|
||||
)
|
||||
component = CharField(required=False)
|
||||
title = CharField(required=False)
|
||||
|
||||
|
@ -90,7 +92,7 @@ class ChallengeResponse(Serializer):
|
|||
|
||||
stage: Optional["StageView"]
|
||||
|
||||
def __init__(self, instance, data, **kwargs):
|
||||
def __init__(self, instance=None, data=None, **kwargs):
|
||||
self.stage = kwargs.pop("stage", None)
|
||||
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.response import HttpResponse
|
||||
from django.views.generic.base import View
|
||||
from rest_framework.request import Request
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import DEFAULT_AVATAR, User
|
||||
|
@ -67,9 +68,9 @@ class ChallengeStageView(StageView):
|
|||
return HttpChallengeResponse(challenge)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
def post(self, request: Request, *args, **kwargs) -> HttpResponse:
|
||||
"""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():
|
||||
return self.challenge_invalid(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.views.decorators.clickjacking import xframe_options_sameorigin
|
||||
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 authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||
from authentik.events.models import cleanse_dict
|
||||
from authentik.flows.challenge import (
|
||||
Challenge,
|
||||
ChallengeResponse,
|
||||
ChallengeTypes,
|
||||
HttpChallengeResponse,
|
||||
RedirectChallenge,
|
||||
|
@ -40,9 +45,11 @@ SESSION_KEY_GET = "authentik_flows_get"
|
|||
|
||||
|
||||
@method_decorator(xframe_options_sameorigin, name="dispatch")
|
||||
class FlowExecutorView(View):
|
||||
class FlowExecutorView(APIView):
|
||||
"""Stage 1 Flow executor, passing requests to Stage Views"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
flow: Flow
|
||||
|
||||
plan: Optional[FlowPlan] = None
|
||||
|
@ -113,8 +120,13 @@ class FlowExecutorView(View):
|
|||
self.current_stage_view.request = 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:
|
||||
"""pass get request to current stage"""
|
||||
"""Get the next pending challenge from the currently active flow."""
|
||||
self._logger.debug(
|
||||
"f(exec): Passing GET",
|
||||
view_class=class_to_path(self.current_stage_view.__class__),
|
||||
|
@ -127,8 +139,13 @@ class FlowExecutorView(View):
|
|||
self._logger.exception(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:
|
||||
"""pass post request to current stage"""
|
||||
"""Solve the previously retrieved challenge and advanced to the next stage."""
|
||||
self._logger.debug(
|
||||
"f(exec): Passing POST",
|
||||
view_class=class_to_path(self.current_stage_view.__class__),
|
||||
|
@ -175,8 +192,10 @@ class FlowExecutorView(View):
|
|||
"f(exec): Continuing with next stage",
|
||||
reamining=len(self.plan.stages),
|
||||
)
|
||||
kwargs = self.kwargs
|
||||
kwargs.update({"flow_slug": self.flow.slug})
|
||||
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
|
||||
self._logger.debug(
|
||||
|
|
|
@ -74,7 +74,7 @@ class SAMLFlowFinalView(ChallengeStageView):
|
|||
return super().get(
|
||||
self.request,
|
||||
**{
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-autosubmit",
|
||||
"title": "Redirecting to %(app)s..." % {"app": application.name},
|
||||
"url": provider.acs_url,
|
||||
|
|
|
@ -31,7 +31,7 @@ class AuthenticatorStaticStageView(ChallengeStageView):
|
|||
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
|
||||
return AuthenticatorStaticChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-static",
|
||||
"codes": [token.token for token in tokens],
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
|
|||
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
||||
return AuthenticatorTOTPChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-totp",
|
||||
"config_url": device.config_url,
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||
challenges = self.request.session["device_challenges"]
|
||||
return AuthenticatorChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-validate",
|
||||
"device_challenges": challenges,
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||
|
||||
return AuthenticatorWebAuthnChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-webauthn",
|
||||
"registration": make_credential_options.registration_dict,
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ class CaptchaStageView(ChallengeStageView):
|
|||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
return CaptchaChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-captcha",
|
||||
"site_key": self.executor.current_stage.public_key,
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class ConsentStageView(ChallengeStageView):
|
|||
def get_challenge(self) -> Challenge:
|
||||
challenge = ConsentChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-consent",
|
||||
}
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ class DummyStageView(ChallengeStageView):
|
|||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
return DummyChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "",
|
||||
"title": self.executor.current_stage.name,
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ class EmailStageView(ChallengeStageView):
|
|||
|
||||
def get_challenge(self) -> Challenge:
|
||||
challenge = EmailChallenge(
|
||||
data={"type": ChallengeTypes.native, "component": "ak-stage-email"}
|
||||
data={"type": ChallengeTypes.native.value, "component": "ak-stage-email"}
|
||||
)
|
||||
return challenge
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ class IdentificationStageView(ChallengeStageView):
|
|||
current_stage: IdentificationStage = self.executor.current_stage
|
||||
challenge = IdentificationChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-identification",
|
||||
"primary_action": _("Log in"),
|
||||
"input_type": "text",
|
||||
|
|
|
@ -78,7 +78,7 @@ class PasswordStageView(ChallengeStageView):
|
|||
def get_challenge(self) -> Challenge:
|
||||
challenge = PasswordChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-password",
|
||||
}
|
||||
)
|
||||
|
|
|
@ -164,7 +164,7 @@ class PromptStageView(ChallengeStageView):
|
|||
fields = list(self.executor.current_stage.fields.all().order_by("order"))
|
||||
challenge = PromptChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-prompt",
|
||||
"fields": [PromptSerializer(field).data for field in fields],
|
||||
},
|
||||
|
|
|
@ -279,6 +279,7 @@ stages:
|
|||
displayName: Build static files for e2e
|
||||
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
|
||||
cd web
|
||||
npm i
|
||||
npm run build
|
||||
|
|
|
@ -33,7 +33,7 @@ stages:
|
|||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: 'outpost/pkg/'
|
||||
artifact: 'swagger_client'
|
||||
artifact: 'go_swagger_client'
|
||||
publishLocation: 'pipeline'
|
||||
- stage: lint
|
||||
jobs:
|
||||
|
@ -51,7 +51,7 @@ stages:
|
|||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'swagger_client'
|
||||
artifactName: 'go_swagger_client'
|
||||
path: "outpost/pkg/"
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
|
@ -70,7 +70,7 @@ stages:
|
|||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'swagger_client'
|
||||
artifactName: 'go_swagger_client'
|
||||
path: "outpost/pkg/"
|
||||
- task: Go@0
|
||||
inputs:
|
||||
|
@ -89,7 +89,7 @@ stages:
|
|||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'swagger_client'
|
||||
artifactName: 'go_swagger_client'
|
||||
path: "outpost/pkg/"
|
||||
- task: Bash@3
|
||||
inputs:
|
||||
|
|
165
swagger.yaml
165
swagger.yaml
|
@ -29,10 +29,7 @@ paths:
|
|||
'200':
|
||||
description: Login Metrics per 1h
|
||||
schema:
|
||||
description: ''
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/AdministrationMetrics'
|
||||
$ref: '#/definitions/LoginMetrics'
|
||||
tags:
|
||||
- admin
|
||||
parameters: []
|
||||
|
@ -318,9 +315,12 @@ paths:
|
|||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
description: Coordinates for diagrams
|
||||
schema:
|
||||
$ref: '#/definitions/Application'
|
||||
description: ''
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Coordinate'
|
||||
tags:
|
||||
- core
|
||||
parameters:
|
||||
|
@ -1620,17 +1620,12 @@ paths:
|
|||
description: |-
|
||||
Send example notification using selected transport. Requires
|
||||
Modify permissions.
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/NotificationTransport'
|
||||
parameters: []
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
'200':
|
||||
description: Notification test serializer
|
||||
schema:
|
||||
$ref: '#/definitions/NotificationTransport'
|
||||
$ref: '#/definitions/NotificationTransportTest'
|
||||
tags:
|
||||
- events
|
||||
parameters:
|
||||
|
@ -1822,6 +1817,42 @@ paths:
|
|||
required: true
|
||||
type: string
|
||||
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/:
|
||||
get:
|
||||
operationId: flows_instances_list
|
||||
|
@ -9296,17 +9327,33 @@ paths:
|
|||
type: string
|
||||
format: uuid
|
||||
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
|
||||
type: object
|
||||
properties:
|
||||
logins_per_1h:
|
||||
title: Logins per 1h
|
||||
type: string
|
||||
description: ''
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Coordinate'
|
||||
readOnly: true
|
||||
logins_failed_per_1h:
|
||||
title: Logins failed per 1h
|
||||
type: string
|
||||
description: ''
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Coordinate'
|
||||
readOnly: true
|
||||
Task:
|
||||
description: Serialize TaskInfo and TaskResult
|
||||
|
@ -9332,11 +9379,11 @@ definitions:
|
|||
format: date-time
|
||||
status:
|
||||
title: Status
|
||||
type: integer
|
||||
type: string
|
||||
enum:
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- SUCCESSFUL
|
||||
- WARNING
|
||||
- ERROR
|
||||
messages:
|
||||
description: ''
|
||||
type: array
|
||||
|
@ -9728,8 +9775,6 @@ definitions:
|
|||
type: integer
|
||||
Notification:
|
||||
description: Notification Serializer
|
||||
required:
|
||||
- event
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
|
@ -9897,6 +9942,18 @@ definitions:
|
|||
webhook_url:
|
||||
title: Webhook url
|
||||
type: string
|
||||
NotificationTransportTest:
|
||||
description: Notification test serializer
|
||||
required:
|
||||
- messages
|
||||
type: object
|
||||
properties:
|
||||
messages:
|
||||
description: ''
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
Flow:
|
||||
description: Flow Serializer
|
||||
required:
|
||||
|
@ -10050,6 +10107,55 @@ definitions:
|
|||
format: uuid
|
||||
readOnly: 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:
|
||||
description: Generic cache stats for an object
|
||||
type: object
|
||||
|
@ -10303,22 +10409,23 @@ definitions:
|
|||
readOnly: true
|
||||
TypeCreate:
|
||||
description: Types of an object that can be created
|
||||
required:
|
||||
- name
|
||||
- description
|
||||
- link
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
link:
|
||||
title: Link
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
ServiceConnectionState:
|
||||
description: Serializer for Service connection state
|
||||
|
|
|
@ -4,3 +4,8 @@ node_modules
|
|||
dist
|
||||
# don't lint nyc coverage output
|
||||
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/', '') }}
|
||||
|
||||
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
|
||||
jobs:
|
||||
- job: eslint
|
||||
|
@ -20,6 +39,11 @@ stages:
|
|||
inputs:
|
||||
versionSpec: '12.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'ts_swagger_client'
|
||||
path: "web/src/api/"
|
||||
- task: Npm@1
|
||||
inputs:
|
||||
command: 'install'
|
||||
|
@ -37,6 +61,11 @@ stages:
|
|||
inputs:
|
||||
versionSpec: '12.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'ts_swagger_client'
|
||||
path: "web/src/api/"
|
||||
- task: Npm@1
|
||||
inputs:
|
||||
command: 'install'
|
||||
|
@ -56,6 +85,11 @@ stages:
|
|||
inputs:
|
||||
versionSpec: '12.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'ts_swagger_client'
|
||||
path: "web/src/api/"
|
||||
- task: Npm@1
|
||||
inputs:
|
||||
command: 'install'
|
||||
|
@ -71,6 +105,11 @@ stages:
|
|||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'ts_swagger_client'
|
||||
path: "web/src/api/"
|
||||
- task: Bash@3
|
||||
inputs:
|
||||
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 {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
|
@ -20,97 +13,20 @@ export interface BaseInheritanceModel {
|
|||
|
||||
}
|
||||
|
||||
export class Client {
|
||||
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 {
|
||||
export interface AKPagination {
|
||||
next?: number;
|
||||
previous?: number;
|
||||
|
||||
count: number;
|
||||
current: number;
|
||||
total_pages: number;
|
||||
totalPages: number;
|
||||
|
||||
start_index: number;
|
||||
end_index: number;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
}
|
||||
|
||||
export interface AKResponse<T> {
|
||||
pagination: PBPagination;
|
||||
pagination: AKPagination;
|
||||
|
||||
results: Array<T>;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
import { DefaultClient } from "./Client";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import { Integrations } from "@sentry/tracing";
|
||||
import { VERSION } from "../constants";
|
||||
import { SentryIgnoredError } from "../common/errors";
|
||||
import { Configuration } from "./runtime";
|
||||
import { RootApi } from "./apis";
|
||||
import { Config } from ".";
|
||||
import { getCookie } from "../utils";
|
||||
|
||||
export class Config {
|
||||
branding_logo: string;
|
||||
branding_title: string;
|
||||
|
||||
error_reporting_enabled: boolean;
|
||||
error_reporting_environment: string;
|
||||
error_reporting_send_pii: boolean;
|
||||
|
||||
constructor() {
|
||||
throw Error();
|
||||
export const DEFAULT_CONFIG = new Configuration({
|
||||
basePath: "/api/v2beta",
|
||||
headers: {
|
||||
"X-CSRFToken": getCookie("authentik_csrf"),
|
||||
}
|
||||
});
|
||||
|
||||
static get(): Promise<Config> {
|
||||
return DefaultClient.fetch<Config>(["root", "config"]).then((config) => {
|
||||
if (config.error_reporting_enabled) {
|
||||
export function configureSentry(): Promise<Config> {
|
||||
return new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => {
|
||||
if (config.errorReportingEnabled) {
|
||||
Sentry.init({
|
||||
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
|
||||
release: `authentik@${VERSION}`,
|
||||
|
@ -26,7 +24,7 @@ export class Config {
|
|||
new Integrations.BrowserTracing(),
|
||||
],
|
||||
tracesSampleRate: 0.6,
|
||||
environment: config.error_reporting_environment,
|
||||
environment: config.errorReportingEnvironment,
|
||||
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
|
||||
if (hint.originalException instanceof SentryIgnoredError) {
|
||||
return null;
|
||||
|
@ -38,5 +36,4 @@ export class 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 {
|
||||
pk: number;
|
||||
|
@ -11,37 +11,7 @@ export interface EventContext {
|
|||
[key: string]: EventContext | string | number | string[];
|
||||
}
|
||||
|
||||
export class Event {
|
||||
pk: string;
|
||||
export interface EventWithContext extends Event {
|
||||
user: EventUser;
|
||||
action: string;
|
||||
app: string;
|
||||
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 { TypeCreate } from "./Providers";
|
||||
|
||||
export enum ChallengeTypes {
|
||||
native = "native",
|
||||
response = "response",
|
||||
shell = "shell",
|
||||
redirect = "redirect",
|
||||
}
|
||||
import { ChallengeTypeEnum } from "./models";
|
||||
|
||||
export interface Error {
|
||||
code: string;
|
||||
|
@ -18,11 +10,12 @@ export interface ErrorDict {
|
|||
}
|
||||
|
||||
export interface Challenge {
|
||||
type: ChallengeTypes;
|
||||
type: ChallengeTypeEnum;
|
||||
component?: string;
|
||||
title?: string;
|
||||
response_errors?: ErrorDict;
|
||||
}
|
||||
|
||||
export interface WithUserInfoChallenge extends Challenge {
|
||||
pending_user: string;
|
||||
pending_user_avatar: string;
|
||||
|
@ -31,6 +24,7 @@ export interface WithUserInfoChallenge extends Challenge {
|
|||
export interface ShellChallenge extends Challenge {
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface RedirectChallenge extends Challenge {
|
||||
to: string;
|
||||
}
|
||||
|
@ -44,104 +38,3 @@ export enum FlowDesignation {
|
|||
Recovery = "recovery",
|
||||
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>;
|
||||
|
||||
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> {
|
||||
export function me(): Promise<User> {
|
||||
if (!_globalMePromise) {
|
||||
_globalMePromise = DefaultClient.fetch<User>(["core", "users", "me"]);
|
||||
_globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe({});
|
||||
}
|
||||
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;
|
||||
}
|
||||
/* tabs, vertical */
|
||||
.pf-c-tabs__link {
|
||||
.pf-c-tabs.pf-m-vertical .pf-c-tabs__link {
|
||||
background-color: var(--ak-dark-background-light);
|
||||
}
|
||||
/* 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 { ERROR_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||
import { SpinnerButton } from "./SpinnerButton";
|
||||
|
@ -12,26 +11,16 @@ export class ActionButton extends SpinnerButton {
|
|||
@property()
|
||||
method = "POST";
|
||||
|
||||
@property({attribute: false})
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
apiRequest: () => Promise<any> = () => { throw new Error(); };
|
||||
|
||||
callAction(): void {
|
||||
if (this.isRunning === true) {
|
||||
return;
|
||||
}
|
||||
this.setLoading();
|
||||
const csrftoken = getCookie("authentik_csrf");
|
||||
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.apiRequest().then(() => {
|
||||
this.setDone(SUCCESS_CLASS);
|
||||
})
|
||||
.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";
|
||||
// @ts-ignore
|
||||
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 { ColorStyles } from "../../common/styles";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
|
||||
@customElement("ak-token-copy-button")
|
||||
export class TokenCopyButton extends LitElement {
|
||||
|
@ -36,8 +37,14 @@ export class TokenCopyButton extends LitElement {
|
|||
}, 1500);
|
||||
return;
|
||||
}
|
||||
Token.getKey(this.identifier).then((token) => {
|
||||
navigator.clipboard.writeText(token).then(() => {
|
||||
new CoreApi(DEFAULT_CONFIG).coreTokensViewKey({
|
||||
identifier: this.identifier
|
||||
}).then((token) => {
|
||||
if (!token.key) {
|
||||
this.buttonClass = ERROR_CLASS;
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(token.key).then(() => {
|
||||
this.buttonClass = SUCCESS_CLASS;
|
||||
setTimeout(() => {
|
||||
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 {
|
||||
if (navigator.webdriver) return;
|
||||
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
|
||||
window.location.host
|
||||
}/ws/client/`;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { gettext } from "django";
|
||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { EventsApi, Notification } from "../../api";
|
||||
import { AKResponse } from "../../api/Client";
|
||||
import { Notification } from "../../api/EventNotification";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
|
||||
@customElement("ak-notification-drawer")
|
||||
|
@ -30,9 +31,9 @@ export class NotificationDrawer extends LitElement {
|
|||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
Notification.list({
|
||||
seen: false,
|
||||
ordering: "-created"
|
||||
new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
|
||||
seen: "false",
|
||||
ordering: "-created",
|
||||
}).then(r => {
|
||||
this.notifications = r;
|
||||
this.unread = r.results.length;
|
||||
|
@ -40,7 +41,6 @@ export class NotificationDrawer extends LitElement {
|
|||
}
|
||||
|
||||
renderItem(item: Notification): TemplateResult {
|
||||
const created = new Date(parseInt(item.created, 10) * 1000);
|
||||
let level = "";
|
||||
switch (item.severity) {
|
||||
case "notice":
|
||||
|
@ -66,15 +66,18 @@ export class NotificationDrawer extends LitElement {
|
|||
</div>
|
||||
<div class="pf-c-notification-drawer__list-item-action">
|
||||
<button class="pf-c-dropdown__toggle pf-m-plain" type="button" @click=${() => {
|
||||
Notification.markSeen(item.pk).then(() => {
|
||||
this.firstUpdated();
|
||||
new EventsApi(DEFAULT_CONFIG).eventsNotificationsPartialUpdate({
|
||||
uuid: item.pk || "",
|
||||
data: {
|
||||
seen: true,
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<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>`;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,16 +2,16 @@ import { gettext } from "django";
|
|||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { AKResponse } from "../../api/Client";
|
||||
import { Table, TableColumn } from "../../elements/table/Table";
|
||||
import { PolicyBinding } from "../../api/PolicyBindings";
|
||||
import { PoliciesApi, PolicyBinding } from "../../api";
|
||||
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/AdminLoginsChart";
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/buttons/Dropdown";
|
||||
import { Policy } from "../../api/Policies";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import { AdminURLManager } from "../../api/legacy";
|
||||
|
||||
@customElement("ak-bound-policies-list")
|
||||
export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
|
@ -19,11 +19,11 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
target?: string;
|
||||
|
||||
apiEndpoint(page: number): Promise<AKResponse<PolicyBinding>> {
|
||||
return PolicyBinding.list({
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
||||
target: this.target || "",
|
||||
ordering: "order",
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
pageSize: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -56,13 +56,13 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
html`${item.order}`,
|
||||
html`${item.timeout}`,
|
||||
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">
|
||||
${gettext("Edit")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</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">
|
||||
${gettext("Delete")}
|
||||
</ak-spinner-button>
|
||||
|
@ -78,7 +78,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
${gettext("No policies are currently bound to this object.")}
|
||||
</div>
|
||||
<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">
|
||||
${gettext("Bind Policy")}
|
||||
</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>
|
||||
</button>
|
||||
<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 html`<li>
|
||||
<ak-modal-button href="${type.link}">
|
||||
|
@ -110,7 +110,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
}), html`<ak-spinner></ak-spinner>`)}
|
||||
</ul>
|
||||
</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">
|
||||
${gettext("Bind Policy")}
|
||||
</ak-spinner-button>
|
||||
|
|
|
@ -3,15 +3,17 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
|
|||
import PageStyle from "@patternfly/patternfly/components/Page/page.css";
|
||||
// @ts-ignore
|
||||
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 = {
|
||||
branding_logo: " /static/dist/assets/icons/icon_left_brand.svg",
|
||||
branding_title: "authentik",
|
||||
brandingLogo: " /static/dist/assets/icons/icon_left_brand.svg",
|
||||
brandingTitle: "authentik",
|
||||
|
||||
error_reporting_enabled: false,
|
||||
error_reporting_environment: "",
|
||||
error_reporting_send_pii: false,
|
||||
errorReportingEnabled: false,
|
||||
errorReportingEnvironment: "",
|
||||
errorReportingSendPii: false,
|
||||
};
|
||||
|
||||
@customElement("ak-sidebar-brand")
|
||||
|
@ -40,13 +42,13 @@ export class SidebarBrand extends LitElement {
|
|||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
Config.get().then((c) => (this.config = c));
|
||||
configureSentry().then((c) => {this.config = c;});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <a href="#/" class="pf-c-page__header-brand-link">
|
||||
<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>
|
||||
</a>`;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ import NavStyle from "@patternfly/patternfly/components/Nav/nav.css";
|
|||
import fa from "@fortawesome/fontawesome-free/css/all.css";
|
||||
// @ts-ignore
|
||||
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 "../notifications/NotificationTrigger";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
|
||||
@customElement("ak-sidebar-user")
|
||||
export class SidebarUser extends LitElement {
|
||||
|
@ -37,8 +38,8 @@ export class SidebarUser extends LitElement {
|
|||
render(): TemplateResult {
|
||||
return html`
|
||||
<a href="#/-/user/" class="pf-c-nav__link user-avatar" id="user-settings">
|
||||
${until(User.me().then((u) => {
|
||||
return html`<img class="pf-c-avatar" src="${u.avatar}" alt="" />`;
|
||||
${until(me().then((u) => {
|
||||
return html`<img class="pf-c-avatar" src="${ifDefined(u.avatar)}" alt="" />`;
|
||||
}), html``)}
|
||||
</a>
|
||||
<ak-notification-trigger class="pf-c-nav__link user-notifications">
|
||||
|
|
|
@ -5,7 +5,7 @@ import { COMMON_STYLES } from "../../common/styles";
|
|||
|
||||
import "./TablePagination";
|
||||
import "../EmptyState";
|
||||
|
||||
import "../Spinner";
|
||||
|
||||
export class TableColumn {
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
import { PBPagination } from "../../api/Client";
|
||||
import { AKPagination } from "../../api/Client";
|
||||
import { gettext } from "django";
|
||||
|
||||
@customElement("ak-table-pagination")
|
||||
export class TablePagination extends LitElement {
|
||||
@property({attribute: false})
|
||||
pages?: PBPagination;
|
||||
pages?: AKPagination;
|
||||
|
||||
@property({attribute: false})
|
||||
// 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__toggle pf-m-text pf-m-plain">
|
||||
<span class="pf-c-options-menu__toggle-text">
|
||||
${this.pages?.start_index} -
|
||||
${this.pages?.end_index} of
|
||||
${this.pages?.startIndex} -
|
||||
${this.pages?.endIndex} of
|
||||
${this.pages?.count}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import "construct-style-sheets-polyfill";
|
||||
|
||||
import "./pages/generic/FlowExecutor";
|
||||
import "./flows/FlowExecutor";
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
import { gettext } from "django";
|
||||
import { LitElement, html, customElement, property, TemplateResult, CSSResult, css } from "lit-element";
|
||||
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
||||
import { getCookie } from "../../utils";
|
||||
import "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
||||
import "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||
import "../../elements/stages/authenticator_validate/AuthenticatorValidateStage";
|
||||
import "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||
import "../../elements/stages/autosubmit/AutosubmitStage";
|
||||
import "../../elements/stages/captcha/CaptchaStage";
|
||||
import "../../elements/stages/consent/ConsentStage";
|
||||
import "../../elements/stages/email/EmailStage";
|
||||
import "../../elements/stages/identification/IdentificationStage";
|
||||
import "../../elements/stages/password/PasswordStage";
|
||||
import "../../elements/stages/prompt/PromptStage";
|
||||
import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows";
|
||||
import { DefaultClient } from "../../api/Client";
|
||||
import { IdentificationChallenge } from "../../elements/stages/identification/IdentificationStage";
|
||||
import { PasswordChallenge } from "../../elements/stages/password/PasswordStage";
|
||||
import { ConsentChallenge } from "../../elements/stages/consent/ConsentStage";
|
||||
import { EmailChallenge } from "../../elements/stages/email/EmailStage";
|
||||
import { AutosubmitChallenge } from "../../elements/stages/autosubmit/AutosubmitStage";
|
||||
import { PromptChallenge } from "../../elements/stages/prompt/PromptStage";
|
||||
import { AuthenticatorTOTPChallenge } from "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||
import { AuthenticatorStaticChallenge } from "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
||||
import { AuthenticatorValidateStageChallenge } from "../../elements/stages/authenticator_validate/AuthenticatorValidateStage";
|
||||
import { WebAuthnAuthenticatorRegisterChallenge } from "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||
import { CaptchaChallenge } from "../../elements/stages/captcha/CaptchaStage";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
import { SpinnerSize } from "../../elements/Spinner";
|
||||
import { StageHost } from "../../elements/stages/base";
|
||||
import "./stages/authenticator_static/AuthenticatorStaticStage";
|
||||
import "./stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||
import "./stages/authenticator_validate/AuthenticatorValidateStage";
|
||||
import "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||
import "./stages/autosubmit/AutosubmitStage";
|
||||
import "./stages/captcha/CaptchaStage";
|
||||
import "./stages/consent/ConsentStage";
|
||||
import "./stages/email/EmailStage";
|
||||
import "./stages/identification/IdentificationStage";
|
||||
import "./stages/password/PasswordStage";
|
||||
import "./stages/prompt/PromptStage";
|
||||
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
|
||||
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
|
||||
import { PasswordChallenge } from "./stages/password/PasswordStage";
|
||||
import { ConsentChallenge } from "./stages/consent/ConsentStage";
|
||||
import { EmailChallenge } from "./stages/email/EmailStage";
|
||||
import { AutosubmitChallenge } from "./stages/autosubmit/AutosubmitStage";
|
||||
import { PromptChallenge } from "./stages/prompt/PromptStage";
|
||||
import { AuthenticatorTOTPChallenge } from "./stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||
import { AuthenticatorStaticChallenge } from "./stages/authenticator_static/AuthenticatorStaticStage";
|
||||
import { AuthenticatorValidateStageChallenge } from "./stages/authenticator_validate/AuthenticatorValidateStage";
|
||||
import { WebAuthnAuthenticatorRegisterChallenge } from "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||
import { CaptchaChallenge } from "./stages/captcha/CaptchaStage";
|
||||
import { COMMON_STYLES } from "../common/styles";
|
||||
import { SpinnerSize } from "../elements/Spinner";
|
||||
import { StageHost } from "./stages/base";
|
||||
import { Challenge, ChallengeTypeEnum, FlowsApi } from "../api";
|
||||
import { DEFAULT_CONFIG } from "../api/Config";
|
||||
|
||||
@customElement("ak-flow-executor")
|
||||
export class FlowExecutor extends LitElement implements StageHost {
|
||||
|
@ -68,37 +68,30 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
});
|
||||
}
|
||||
|
||||
submit(formData?: FormData): Promise<void> {
|
||||
const csrftoken = getCookie("authentik_csrf");
|
||||
const request = new Request(DefaultClient.makeUrl(["flows", "executor", this.flowSlug]), {
|
||||
headers: {
|
||||
"X-CSRFToken": csrftoken,
|
||||
},
|
||||
});
|
||||
submit<T>(formData?: T): Promise<void> {
|
||||
this.loading = true;
|
||||
return fetch(request, {
|
||||
method: "POST",
|
||||
mode: "same-origin",
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolveRaw({
|
||||
flowSlug: this.flowSlug,
|
||||
data: formData || {},
|
||||
}).then((challengeRaw) => {
|
||||
return challengeRaw.raw.json();
|
||||
}).then((data) => {
|
||||
this.challenge = data;
|
||||
})
|
||||
.catch((e) => {
|
||||
}).catch((e) => {
|
||||
this.errorMessage(e);
|
||||
})
|
||||
.finally(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
this.loading = true;
|
||||
Flow.executor(this.flowSlug).then((challenge) => {
|
||||
this.challenge = challenge;
|
||||
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGetRaw({
|
||||
flowSlug: this.flowSlug
|
||||
}).then((challengeRaw) => {
|
||||
return challengeRaw.raw.json();
|
||||
}).then((challenge) => {
|
||||
this.challenge = challenge as Challenge;
|
||||
}).catch((e) => {
|
||||
// Catch JSON or Update errors
|
||||
this.errorMessage(e);
|
||||
|
@ -109,7 +102,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
|
||||
errorMessage(error: string): void {
|
||||
this.challenge = <ShellChallenge>{
|
||||
type: ChallengeTypes.shell,
|
||||
type: ChallengeTypeEnum.Shell,
|
||||
body: `<style>
|
||||
.ak-exception {
|
||||
font-family: monospace;
|
||||
|
@ -139,13 +132,13 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
return this.renderLoading();
|
||||
}
|
||||
switch (this.challenge.type) {
|
||||
case ChallengeTypes.redirect:
|
||||
case ChallengeTypeEnum.Redirect:
|
||||
console.debug(`authentik/flows: redirecting to ${(this.challenge as RedirectChallenge).to}`);
|
||||
window.location.assign((this.challenge as RedirectChallenge).to);
|
||||
return this.renderLoading();
|
||||
case ChallengeTypes.shell:
|
||||
case ChallengeTypeEnum.Shell:
|
||||
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
|
||||
case ChallengeTypes.native:
|
||||
case ChallengeTypeEnum.Native:
|
||||
switch (this.challenge.component) {
|
||||
case "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 { BaseStage } from "../base";
|
||||
import "../form";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
export interface AuthenticatorStaticChallenge extends WithUserInfoChallenge {
|
||||
codes: number[];
|
|
@ -5,7 +5,8 @@ import { COMMON_STYLES } from "../../../common/styles";
|
|||
import { BaseStage } from "../base";
|
||||
import "webcomponent-qr-code";
|
||||
import "../form";
|
||||
import { showMessage } from "../../messages/MessageContainer";
|
||||
import { showMessage } from "../../../elements/messages/MessageContainer";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
export interface AuthenticatorTOTPChallenge extends WithUserInfoChallenge {
|
||||
config_url: string;
|
|
@ -36,8 +36,8 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
|
|||
@property({attribute: false})
|
||||
selectedDeviceChallenge?: DeviceChallenge;
|
||||
|
||||
submit(formData?: FormData): Promise<void> {
|
||||
return this.host?.submit(formData) || Promise.resolve();
|
||||
submit<T>(formData?: T): Promise<void> {
|
||||
return this.host?.submit<T>(formData) || Promise.resolve();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
|
@ -4,6 +4,7 @@ import { COMMON_STYLES } from "../../../common/styles";
|
|||
import { BaseStage } from "../base";
|
||||
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
|
||||
import "../form";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
@customElement("ak-stage-authenticator-validate-code")
|
||||
export class AuthenticatorValidateStageWebCode extends BaseStage {
|
|
@ -1,7 +1,7 @@
|
|||
import { gettext } from "django";
|
||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { COMMON_STYLES } from "../../../common/styles";
|
||||
import { SpinnerSize } from "../../Spinner";
|
||||
import { SpinnerSize } from "../../../elements/Spinner";
|
||||
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
|
||||
import { BaseStage } from "../base";
|
||||
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
|
|
@ -1,7 +1,7 @@
|
|||
import { gettext } from "django";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||
import { SpinnerSize } from "../../Spinner";
|
||||
import { SpinnerSize } from "../../../elements/Spinner";
|
||||
import { BaseStage } from "../base";
|
||||
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 { COMMON_STYLES } from "../../../common/styles";
|
||||
import { BaseStage } from "../base";
|
||||
import "../../Spinner";
|
||||
import "../../../elements/Spinner";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
export interface AutosubmitChallenge extends WithUserInfoChallenge {
|
||||
url: string;
|
|
@ -1,7 +1,7 @@
|
|||
import { LitElement } from "lit-element";
|
||||
|
||||
export interface StageHost {
|
||||
submit(formData?: FormData): Promise<void>;
|
||||
submit<T>(formData?: T): Promise<void>;
|
||||
}
|
||||
|
||||
export class BaseStage extends LitElement {
|
||||
|
@ -10,8 +10,12 @@ export class BaseStage extends LitElement {
|
|||
|
||||
submitForm(e: Event): void {
|
||||
e.preventDefault();
|
||||
const object: {
|
||||
[key: string]: unknown;
|
||||
} = {};
|
||||
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 { WithUserInfoChallenge } from "../../../api/Flows";
|
||||
import { COMMON_STYLES } from "../../../common/styles";
|
||||
import { SpinnerSize } from "../../Spinner";
|
||||
import { SpinnerSize } from "../../../elements/Spinner";
|
||||
import { BaseStage } from "../base";
|
||||
import "../form";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
export interface CaptchaChallenge extends WithUserInfoChallenge {
|
||||
site_key: string;
|
|
@ -3,6 +3,7 @@ import { CSSResult, customElement, html, property, TemplateResult } from "lit-el
|
|||
import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||
import { COMMON_STYLES } from "../../../common/styles";
|
||||
import { BaseStage } from "../base";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
export interface Permission {
|
||||
name: string;
|
|
@ -1,10 +1,11 @@
|
|||
import { gettext } from "django";
|
||||
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 { BaseStage } from "../base";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
export type EmailChallenge = Challenge
|
||||
export type EmailChallenge = Challenge;
|
||||
|
||||
@customElement("ak-stage-email")
|
||||
export class EmailStage extends BaseStage {
|
|
@ -1,9 +1,10 @@
|
|||
import { gettext } from "django";
|
||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { Challenge } from "../../../api/Flows";
|
||||
import { COMMON_STYLES } from "../../../common/styles";
|
||||
import { BaseStage } from "../base";
|
||||
import "../form";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
import { Challenge } from "../../../api/Flows";
|
||||
|
||||
export interface IdentificationChallenge extends Challenge {
|
||||
|
|
@ -4,6 +4,7 @@ import { WithUserInfoChallenge } from "../../../api/Flows";
|
|||
import { COMMON_STYLES } from "../../../common/styles";
|
||||
import { BaseStage } from "../base";
|
||||
import "../form";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
|
||||
export interface PasswordChallenge extends WithUserInfoChallenge {
|
||||
recovery_url?: string;
|
|
@ -1,10 +1,11 @@
|
|||
import { gettext } from "django";
|
||||
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
||||
import { Challenge } from "../../../api/Flows";
|
||||
import { COMMON_STYLES } from "../../../common/styles";
|
||||
import { BaseStage } from "../base";
|
||||
import "../form";
|
||||
import "../../../elements/utils/LoadingState";
|
||||
import { Challenge } from "../../../api/Flows";
|
||||
|
||||
export interface Prompt {
|
||||
field_key: string;
|
|
@ -1,5 +1,5 @@
|
|||
import { customElement } from "lit-element";
|
||||
import { User } from "../api/Users";
|
||||
import { me } from "../api/Users";
|
||||
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "../elements/router/Route";
|
||||
import { Interface } from "./Interface";
|
||||
|
@ -10,7 +10,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
|||
new SidebarItem("Overview", "/administration/overview"),
|
||||
new SidebarItem("System Tasks", "/administration/system-tasks"),
|
||||
).when((): Promise<boolean> => {
|
||||
return User.me().then(u => u.is_superuser);
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Events").children(
|
||||
new SidebarItem("Log", "/events/log").activeWhen(
|
||||
|
@ -19,7 +19,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
|||
new SidebarItem("Notification Rules", "/events/rules"),
|
||||
new SidebarItem("Notification Transports", "/events/transports"),
|
||||
).when((): Promise<boolean> => {
|
||||
return User.me().then(u => u.is_superuser);
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Resources").children(
|
||||
new SidebarItem("Applications", "/core/applications").activeWhen(
|
||||
|
@ -34,13 +34,13 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
|||
new SidebarItem("Outposts", "/outpost/outposts"),
|
||||
new SidebarItem("Outpost Service Connections", "/outpost/service-connections"),
|
||||
).when((): Promise<boolean> => {
|
||||
return User.me().then(u => u.is_superuser);
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Customisation").children(
|
||||
new SidebarItem("Policies", "/policy/policies"),
|
||||
new SidebarItem("Property Mappings", "/core/property-mappings"),
|
||||
).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", "/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("Invitations", "/flow/stages/invitations"),
|
||||
).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("User", "/identity/users"),
|
||||
|
@ -56,7 +56,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
|||
new SidebarItem("Certificates", "/crypto/certificates"),
|
||||
new SidebarItem("Tokens", "/core/tokens"),
|
||||
).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";
|
||||
|
||||
// Elements that are used by SiteShell pages
|
||||
// And can't dynamically be imported
|
||||
import "./elements/buttons/ActionButton";
|
||||
import "./elements/buttons/Dropdown";
|
||||
import "./elements/buttons/ModalButton";
|
||||
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/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/LibraryPage";
|
||||
|
||||
import "./pages/generic/SiteShell";
|
||||
import "./interfaces/AdminInterface";
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { gettext } from "django";
|
||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { Application } from "../api/Applications";
|
||||
import { Application, CoreApi } from "../api";
|
||||
import { AKResponse } from "../api/Client";
|
||||
import { DEFAULT_CONFIG } from "../api/Config";
|
||||
import { COMMON_STYLES } from "../common/styles";
|
||||
import { loading, truncate } from "../utils";
|
||||
|
||||
|
@ -31,19 +32,19 @@ export class LibraryApplication extends LitElement {
|
|||
if (!this.application) {
|
||||
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">
|
||||
${this.application.meta_icon
|
||||
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.meta_icon)}" alt="Application Icon"/>`
|
||||
${this.application.metaIcon
|
||||
? 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>`}
|
||||
</div>
|
||||
<div class="pf-c-card__title">
|
||||
<p id="card-1-check-label">${this.application.name}</p>
|
||||
<div class="pf-c-content">
|
||||
<small>${this.application.meta_publisher}</small>
|
||||
<small>${this.application.metaPublisher}</small>
|
||||
</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>`;
|
||||
}
|
||||
|
||||
|
@ -64,7 +65,9 @@ export class LibraryPage extends LitElement {
|
|||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
Application.list().then((r) => (this.apps = r));
|
||||
new CoreApi(DEFAULT_CONFIG).coreApplicationsList({}).then((apps) => {
|
||||
this.apps = apps;
|
||||
});
|
||||
}
|
||||
|
||||
renderEmptyState(): TemplateResult {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { gettext } from "django";
|
|||
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
|
||||
import "../../elements/AdminLoginsChart";
|
||||
import "../../elements/charts/AdminLoginsChart";
|
||||
import "../../elements/cards/AggregatePromiseCard";
|
||||
import "./TopApplicationsTable";
|
||||
import "./cards/AdminStatusCard";
|
||||
|
@ -30,7 +30,7 @@ export class AdminOverviewPage extends LitElement {
|
|||
<section class="pf-c-page__main-section">
|
||||
<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-admin-logins-chart .url="${["admin", "metrics"]}"></ak-admin-logins-chart>
|
||||
<ak-charts-admin-login></ak-charts-admin-login>
|
||||
</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-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