Merge branch 'main' into multi-tenant-django-tenants

This commit is contained in:
Marc 'risson' Schmitt 2023-12-07 09:30:51 +01:00
commit 2724d1d85c
No known key found for this signature in database
GPG Key ID: 9C3FA22FABF1AA8D
70 changed files with 2941 additions and 2126 deletions

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v4 - uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- name: Prepare and generate API - name: Prepare and generate API
@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v4 - uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- name: Setup authentik env - name: Setup authentik env
@ -125,7 +125,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-go@v4 - uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

View File

@ -67,7 +67,7 @@ jobs:
- radius - radius
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v4 - uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- name: Set up QEMU - name: Set up QEMU
@ -126,7 +126,7 @@ jobs:
goarch: [amd64, arm64] goarch: [amd64, arm64]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v4 - uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

View File

@ -35,7 +35,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build RUN npm run build
# Stage 3: Build go proxy # Stage 3: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS go-builder FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.5-bookworm AS go-builder
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH

View File

@ -38,7 +38,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
managed = ReadOnlyField() managed = ReadOnlyField()
component = SerializerMethodField() component = SerializerMethodField()
icon = ReadOnlyField(source="get_icon") icon = ReadOnlyField(source="icon_url")
def get_component(self, obj: Source) -> str: def get_component(self, obj: Source) -> str:
"""Get object component so that we know how to edit the object""" """Get object component so that we know how to edit the object"""

View File

@ -5,7 +5,7 @@ from json import loads
import django_filters import django_filters
from django.db.models.aggregates import Count from django.db.models.aggregates import Count
from django.db.models.fields.json import KeyTextTransform, KeyTransform from django.db.models.fields.json import KeyTextTransform, KeyTransform
from django.db.models.functions import ExtractDay from django.db.models.functions import ExtractDay, ExtractHour
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
@ -149,7 +149,15 @@ class EventViewSet(ModelViewSet):
return Response(EventTopPerUserSerializer(instance=events, many=True).data) return Response(EventTopPerUserSerializer(instance=events, many=True).data)
@extend_schema( @extend_schema(
methods=["GET"], responses={200: CoordinateSerializer(many=True)},
)
@action(detail=False, methods=["GET"], pagination_class=None)
def volume(self, request: Request) -> Response:
"""Get event volume for specified filters and timeframe"""
queryset = self.filter_queryset(self.get_queryset())
return Response(queryset.get_events_per(timedelta(days=7), ExtractHour, 7 * 3))
@extend_schema(
responses={200: CoordinateSerializer(many=True)}, responses={200: CoordinateSerializer(many=True)},
filters=[], filters=[],
parameters=[ parameters=[

View File

@ -167,7 +167,11 @@ class ChallengeStageView(StageView):
stage_type=self.__class__.__name__, method="get_challenge" stage_type=self.__class__.__name__, method="get_challenge"
).time(), ).time(),
): ):
challenge = self.get_challenge(*args, **kwargs) try:
challenge = self.get_challenge(*args, **kwargs)
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
with Hub.current.start_span( with Hub.current.start_span(
op="authentik.flow.stage._get_challenge", op="authentik.flow.stage._get_challenge",
description=self.__class__.__name__, description=self.__class__.__name__,

View File

@ -5,6 +5,7 @@ from uuid import uuid4
from django.contrib import messages from django.contrib import messages
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict from django.http.request import QueryDict
from django.template.exceptions import TemplateSyntaxError
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.timezone import now from django.utils.timezone import now
@ -12,11 +13,14 @@ from django.utils.translation import gettext as _
from rest_framework.fields import CharField from rest_framework.fields import CharField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.exceptions import StageInvalidException
from authentik.flows.models import FlowDesignation, FlowToken from authentik.flows.models import FlowDesignation, FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
from authentik.lib.utils.errors import exception_to_string
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils import TemplateEmailMessage
@ -103,18 +107,27 @@ class EmailStageView(ChallengeStageView):
current_stage: EmailStage = self.executor.current_stage current_stage: EmailStage = self.executor.current_stage
token = self.get_token() token = self.get_token()
# Send mail to user # Send mail to user
message = TemplateEmailMessage( try:
subject=_(current_stage.subject), message = TemplateEmailMessage(
to=[email], subject=_(current_stage.subject),
language=pending_user.locale(self.request), to=[email],
template_name=current_stage.template, language=pending_user.locale(self.request),
template_context={ template_name=current_stage.template,
"url": self.get_full_url(**{QS_KEY_TOKEN: token.key}), template_context={
"user": pending_user, "url": self.get_full_url(**{QS_KEY_TOKEN: token.key}),
"expires": token.expires, "user": pending_user,
}, "expires": token.expires,
) },
send_mails(current_stage, message) )
send_mails(current_stage, message)
except TemplateSyntaxError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=current_stage.template,
).from_http(self.request)
raise StageInvalidException from exc
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
# Check if the user came back from the email link to verify # Check if the user came back from the email link to verify
@ -135,7 +148,11 @@ class EmailStageView(ChallengeStageView):
return self.executor.stage_invalid() return self.executor.stage_invalid()
# Check if we've already sent the initial e-mail # Check if we've already sent the initial e-mail
if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context: if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context:
self.send_email() try:
self.send_email()
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@ -4,11 +4,20 @@ from pathlib import Path
from shutil import rmtree from shutil import rmtree
from tempfile import mkdtemp, mkstemp from tempfile import mkdtemp, mkstemp
from typing import Any from typing import Any
from unittest.mock import PropertyMock, patch
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.core.mail.backends.locmem import EmailBackend
from django.urls import reverse
from authentik.stages.email.models import get_template_choices from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.email.models import EmailStage, get_template_choices
def get_templates_setting(temp_dir: str) -> dict[str, Any]: def get_templates_setting(temp_dir: str) -> dict[str, Any]:
@ -18,11 +27,18 @@ def get_templates_setting(temp_dir: str) -> dict[str, Any]:
return templates_setting return templates_setting
class TestEmailStageTemplates(TestCase): class TestEmailStageTemplates(FlowTestCase):
"""Email tests""" """Email tests"""
def setUp(self) -> None: def setUp(self) -> None:
self.dir = mkdtemp() self.dir = Path(mkdtemp())
self.user = create_test_admin_user()
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
self.stage = EmailStage.objects.create(
name="email",
)
self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
def tearDown(self) -> None: def tearDown(self) -> None:
rmtree(self.dir) rmtree(self.dir)
@ -38,3 +54,37 @@ class TestEmailStageTemplates(TestCase):
self.assertEqual(len(choices), 3) self.assertEqual(len(choices), 3)
unlink(file) unlink(file)
unlink(file2) unlink(file2)
def test_custom_template_invalid_syntax(self):
"""Test with custom template"""
with open(self.dir / Path("invalid.html"), "w+", encoding="utf-8") as _invalid:
_invalid.write("{% blocktranslate %}")
with self.settings(TEMPLATES=get_templates_setting(self.dir)):
self.stage.template = "invalid.html"
plan = FlowPlan(
flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
)
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
with patch(
"authentik.stages.email.models.EmailStage.backend_class",
PropertyMock(return_value=EmailBackend),
):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
error_message="Unknown error",
)
events = Event.objects.filter(action=EventAction.CONFIGURATION_ERROR)
self.assertEqual(len(events), 1)
event = events.first()
self.assertEqual(
event.context["message"], "Exception occurred while rendering E-mail template"
)
self.assertEqual(event.context["template"], "invalid.html")

2
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-openapi/runtime v0.26.0 github.com/go-openapi/runtime v0.26.0
github.com/go-openapi/strfmt v0.21.7 github.com/go-openapi/strfmt v0.21.8
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.4.0 github.com/google/uuid v1.4.0
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2

4
go.sum
View File

@ -123,8 +123,8 @@ github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6
github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k= github.com/go-openapi/strfmt v0.21.8 h1:VYBUoKYRLAlgKDrIxR/I0lKrztDQ0tuTDrbhLVP8Erg=
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= github.com/go-openapi/strfmt v0.21.8/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=

View File

@ -147,7 +147,11 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{}) fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{})
fg.SetUsers([]int32{flag.UserPk}) fg.SetUsers([]int32{flag.UserPk})
if g.Parent.IsSet() { if g.Parent.IsSet() {
fg.SetParent(*g.Parent.Get()) if p := g.Parent.Get(); p != nil {
fg.SetParent(*p)
} else {
fg.SetParentNil()
}
} }
fg.SetAttributes(g.Attributes) fg.SetAttributes(g.Attributes)
fg.SetIsSuperuser(*g.IsSuperuser) fg.SetIsSuperuser(*g.IsSuperuser)

View File

@ -1,5 +1,5 @@
# Stage 1: Build # Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.5-bookworm AS builder
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ COPY web .
RUN npm run build-proxy RUN npm run build-proxy
# Stage 2: Build # Stage 2: Build
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.5-bookworm AS builder
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH

View File

@ -1,5 +1,5 @@
# Stage 1: Build # Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.5-bookworm AS builder
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH

View File

@ -6365,6 +6365,86 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/events/events/volume/:
get:
operationId: events_events_volume_list
description: Get event volume for specified filters and timeframe
parameters:
- in: query
name: action
schema:
type: string
- in: query
name: client_ip
schema:
type: string
- in: query
name: context_authorized_app
schema:
type: string
description: Context Authorized application
- in: query
name: context_model_app
schema:
type: string
description: Context Model App
- in: query
name: context_model_name
schema:
type: string
description: Context Model Name
- in: query
name: context_model_pk
schema:
type: string
description: Context Model Primary Key
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: tenant_name
schema:
type: string
description: Tenant name
- in: query
name: username
schema:
type: string
description: Username
tags:
- events
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Coordinate'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/events/notifications/: /events/notifications/:
get: get:
operationId: events_notifications_list operationId: events_notifications_list

View File

@ -7,13 +7,13 @@
"name": "@goauthentik/web-tests", "name": "@goauthentik/web-tests",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.1", "@typescript-eslint/parser": "^6.13.2",
"@wdio/cli": "^8.24.6", "@wdio/cli": "^8.24.13",
"@wdio/local-runner": "^8.24.6", "@wdio/local-runner": "^8.24.12",
"@wdio/mocha-framework": "^8.24.6", "@wdio/mocha-framework": "^8.24.12",
"@wdio/spec-reporter": "^8.24.2", "@wdio/spec-reporter": "^8.24.12",
"eslint": "^8.54.0", "eslint": "^8.55.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
@ -332,9 +332,9 @@
} }
}, },
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "2.1.3", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
@ -382,9 +382,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.54.0", "version": "8.55.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -946,16 +946,16 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.2.tgz",
"integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", "integrity": "sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.5.1", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.13.1", "@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/type-utils": "6.13.1", "@typescript-eslint/type-utils": "6.13.2",
"@typescript-eslint/utils": "6.13.1", "@typescript-eslint/utils": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1", "@typescript-eslint/visitor-keys": "6.13.2",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.4", "ignore": "^5.2.4",
@ -981,15 +981,15 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.2.tgz",
"integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", "integrity": "sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "6.13.1", "@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/typescript-estree": "6.13.1", "@typescript-eslint/typescript-estree": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1", "@typescript-eslint/visitor-keys": "6.13.2",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -1009,13 +1009,13 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", "integrity": "sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1" "@typescript-eslint/visitor-keys": "6.13.2"
}, },
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -1026,13 +1026,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.2.tgz",
"integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", "integrity": "sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "6.13.1", "@typescript-eslint/typescript-estree": "6.13.2",
"@typescript-eslint/utils": "6.13.1", "@typescript-eslint/utils": "6.13.2",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
@ -1053,9 +1053,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", "integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -1066,13 +1066,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", "integrity": "sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1", "@typescript-eslint/visitor-keys": "6.13.2",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -1093,17 +1093,17 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.2.tgz",
"integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", "integrity": "sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.13.1", "@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/typescript-estree": "6.13.1", "@typescript-eslint/typescript-estree": "6.13.2",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
@ -1118,12 +1118,12 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", "integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
@ -1141,34 +1141,33 @@
"dev": true "dev": true
}, },
"node_modules/@wdio/cli": { "node_modules/@wdio/cli": {
"version": "8.24.6", "version": "8.24.13",
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.24.6.tgz", "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.24.13.tgz",
"integrity": "sha512-QXRiP1FeGaSmUO24pFhyzP6lZY/FsZAhXyofl3r6TGwTlnw9i4S7C4Te2qQcccgAQq03rdSK058YURPwbiKhmg==", "integrity": "sha512-UG4dvnT6KfnKDsNVn/GeUidi21Pso6N6eu1O5oin9+fP612zpPFrx3/TuYrAfjJb+qmy1QkKq3zX99y+xlp7og==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.1", "@types/node": "^20.1.1",
"@wdio/config": "8.24.6", "@wdio/config": "8.24.12",
"@wdio/globals": "8.24.6", "@wdio/globals": "8.24.12",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.23.0", "@wdio/protocols": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"@wdio/utils": "8.24.6", "@wdio/utils": "8.24.12",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"cli-spinners": "^2.9.0", "cli-spinners": "^2.9.0",
"detect-package-manager": "^3.0.1",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"ejs": "^3.1.9", "ejs": "^3.1.9",
"execa": "^8.0.1", "execa": "^8.0.1",
"import-meta-resolve": "^3.0.0", "import-meta-resolve": "^4.0.0",
"inquirer": "9.2.12", "inquirer": "9.2.12",
"lodash.flattendeep": "^4.4.0", "lodash.flattendeep": "^4.4.0",
"lodash.pickby": "^4.6.0", "lodash.pickby": "^4.6.0",
"lodash.union": "^4.6.0", "lodash.union": "^4.6.0",
"read-pkg-up": "^10.0.0", "read-pkg-up": "^10.0.0",
"recursive-readdir": "^2.2.3", "recursive-readdir": "^2.2.3",
"webdriverio": "8.24.6", "webdriverio": "8.24.12",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
@ -1191,47 +1190,47 @@
} }
}, },
"node_modules/@wdio/config": { "node_modules/@wdio/config": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.24.6.tgz", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.24.12.tgz",
"integrity": "sha512-ZFmd6rB1kgL4k/SjLXbtFTCxvxSf1qzdt/losiTqkqFBYznkTRUBGSoGaVTlkMtHAReiVSK92sICc15JWaCdEA==", "integrity": "sha512-3HW7qG1rIHzOIybV6oHR1CqLghsN0G3Xzs90ZciGL8dYhtcLtYCHwuWmBw4mkaB5xViU4AmZDuj7ChiG8Cr6Qw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"@wdio/utils": "8.24.6", "@wdio/utils": "8.24.12",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"deepmerge-ts": "^5.0.0", "deepmerge-ts": "^5.0.0",
"glob": "^10.2.2", "glob": "^10.2.2",
"import-meta-resolve": "^3.0.0" "import-meta-resolve": "^4.0.0"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
} }
}, },
"node_modules/@wdio/globals": { "node_modules/@wdio/globals": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.24.6.tgz", "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.24.12.tgz",
"integrity": "sha512-v5Sjyix9ddrxPM8DCf0vADUxr21Fx7nWVYS6Z/gkTEhuQbi5svjs6EGjMmErO6tp3CY4SNTUiz+ZFJw9YH4Swg==", "integrity": "sha512-uF26a89Q+6DdqzSfK9suXJNdWYJnsazjzPuq4Xtz6nKdjgmBufSeX1JHV4LxErEu5b/IdzVcMCUKKEvsZPc9vA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"expect-webdriverio": "^4.6.1", "expect-webdriverio": "^4.6.1",
"webdriverio": "8.24.6" "webdriverio": "8.24.12"
} }
}, },
"node_modules/@wdio/local-runner": { "node_modules/@wdio/local-runner": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.24.6.tgz", "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.24.12.tgz",
"integrity": "sha512-fd91CxlVpOpSxg+QuqgdFl66kEtY7R/ohdKBXNhdMXtXFb4EQIGp/igiMBvuTHcHUMHOw3N8KaHfe6YXo+6Qyw==", "integrity": "sha512-Q1lfdSPDEgKwuE1gNucJrkVfgOJLTjtnYGb7Fe7oYUHGDwjkudjSBJYmyx30qFZKfZ4zRqXtaEdys54/0TxibA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/repl": "8.23.1", "@wdio/repl": "8.24.12",
"@wdio/runner": "8.24.6", "@wdio/runner": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"split2": "^4.1.0", "split2": "^4.1.0",
"stream-buffers": "^3.0.2" "stream-buffers": "^3.0.2"
@ -1241,9 +1240,9 @@
} }
}, },
"node_modules/@wdio/logger": { "node_modules/@wdio/logger": {
"version": "8.16.17", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.16.17.tgz", "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.24.12.tgz",
"integrity": "sha512-zeQ41z3T+b4IsrriZZipayXxLNDuGsm7TdExaviNGojPVrIsQUCSd/FvlLHM32b7ZrMyInHenu/zx1cjAZO71g==", "integrity": "sha512-QisOiVIWKTUCf1H7S+DOtC+gruhlpimQrUXfWMTeeh672PvAJYnTpOJDWA+BtXfsikkUYFAzAaq8SeMJk8rqKg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chalk": "^5.1.2", "chalk": "^5.1.2",
@ -1268,16 +1267,16 @@
} }
}, },
"node_modules/@wdio/mocha-framework": { "node_modules/@wdio/mocha-framework": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.24.6.tgz", "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.24.12.tgz",
"integrity": "sha512-qTRU7trzPJKjdlO6r4+YnyauEQ/cTvCJYRl5t2jqsG8y2OoCRsw4qUydzGTxX3YEkmgZjSN845hMNtyWuZUjcg==", "integrity": "sha512-SHN7CYZnDkVUNYxLp8iMV92xcmU/4gq5dqA0pRrK4m5nIU7BoL0flm0kA+ydYUQyNedQh2ru1V63uNyTOyCKAg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.0",
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"@wdio/utils": "8.24.6", "@wdio/utils": "8.24.12",
"mocha": "^10.0.0" "mocha": "^10.0.0"
}, },
"engines": { "engines": {
@ -1285,15 +1284,15 @@
} }
}, },
"node_modules/@wdio/protocols": { "node_modules/@wdio/protocols": {
"version": "8.23.0", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.24.12.tgz",
"integrity": "sha512-2XTzD+lqQP3g8BWn+Bn5BTFzjHqzZNwq7DjlYrb27Bq8nOA+1DEcj3WzQ6V6CktTnKI/LAYKA1IFAF//Azrp/Q==", "integrity": "sha512-QnVj3FkapmVD3h2zoZk+ZQ8gevSj9D9MiIQIy8eOnY4FAneYZ9R9GvoW+mgNcCZO8S8++S/jZHetR8n+8Q808g==",
"dev": true "dev": true
}, },
"node_modules/@wdio/repl": { "node_modules/@wdio/repl": {
"version": "8.23.1", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.23.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz",
"integrity": "sha512-u6zG2cgBm67V5/WlQzadWqLGXs3moH8MOsgoljULQncelSBBZGZ5DyLB4p7jKcUAsKtMjgmFQmIvpQoqmyvdfg==", "integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0" "@types/node": "^20.1.0"
@ -1303,14 +1302,14 @@
} }
}, },
"node_modules/@wdio/reporter": { "node_modules/@wdio/reporter": {
"version": "8.24.2", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.24.2.tgz", "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.24.12.tgz",
"integrity": "sha512-10hTEk4JIIXW8hnwyxTNgby1ZxoJAbXH9d/eMbkEoAwxx/eqaM+ghPs1GSrzOIjjZ3lwz369POEYfJcLkw3g2w==", "integrity": "sha512-FtLzDTBXdgxXf4T9HJQ2bNpYYSKEw//jojFm9XzB4fPwzPeFY3HC+dbePucVW1SSLrVzVxqIOyHiwCLqQ/4cQw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"diff": "^5.0.0", "diff": "^5.0.0",
"object-inspect": "^1.12.0" "object-inspect": "^1.12.0"
}, },
@ -1319,35 +1318,35 @@
} }
}, },
"node_modules/@wdio/runner": { "node_modules/@wdio/runner": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.24.6.tgz", "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.24.12.tgz",
"integrity": "sha512-2dt5F9scy0klYwB/E4JztLo04OaPsqcuZP9WKn+NSIBNug0UrgUcBv5ARJEuE3iUyPWpTeczWkU3UtcdMmjagQ==", "integrity": "sha512-wiwXZWG12YDe7GCYBnZ1xEg3UKi18Rvh4RNQiumjypDOErJit1hOCppbJ37LqLqQu+tfWGfN73j46yR7fQOCHw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/config": "8.24.6", "@wdio/config": "8.24.12",
"@wdio/globals": "8.24.6", "@wdio/globals": "8.24.12",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"@wdio/utils": "8.24.6", "@wdio/utils": "8.24.12",
"deepmerge-ts": "^5.0.0", "deepmerge-ts": "^5.0.0",
"expect-webdriverio": "^4.6.1", "expect-webdriverio": "^4.6.1",
"gaze": "^1.1.2", "gaze": "^1.1.2",
"webdriver": "8.24.6", "webdriver": "8.24.12",
"webdriverio": "8.24.6" "webdriverio": "8.24.12"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
} }
}, },
"node_modules/@wdio/spec-reporter": { "node_modules/@wdio/spec-reporter": {
"version": "8.24.2", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.24.2.tgz", "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.24.12.tgz",
"integrity": "sha512-FJ1+/kgxlw4ong+5PJJcOzLf8B/TzPL9hGeOF2xoWkk2uMsmNBJN2r27pjtfFxA41lw4q+Oav0Vb/RhdCB601Q==", "integrity": "sha512-Ng3ErWK8eESamCYwIr2Uv49+46RvmT8FnmGaJ6irJoAp101K8zENEs1pyqYHJReucN+ka/wM87blfc2k8NEHCA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@wdio/reporter": "8.24.2", "@wdio/reporter": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"chalk": "^5.1.2", "chalk": "^5.1.2",
"easy-table": "^1.2.0", "easy-table": "^1.2.0",
"pretty-ms": "^7.0.0" "pretty-ms": "^7.0.0"
@ -1369,9 +1368,9 @@
} }
}, },
"node_modules/@wdio/types": { "node_modules/@wdio/types": {
"version": "8.24.2", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.24.2.tgz", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.24.12.tgz",
"integrity": "sha512-x7iWF5NM8NfVxziGwLdQ+3sstgSxRoqfmmFEDTDps0oFrN5CgkqcoLkqXJ5u166gvpxpEq0gxZwxkbPC/Lp0cw==", "integrity": "sha512-SaD3OacDiW06DvSgAQ7sDBbpiI9qZRg7eoVYeBg3uSGVtUq84vTETRhhV7D6xTC00IqZu+mmN2TY5/q+7Gqy7w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0" "@types/node": "^20.1.0"
@ -1381,21 +1380,20 @@
} }
}, },
"node_modules/@wdio/utils": { "node_modules/@wdio/utils": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.24.6.tgz", "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.24.12.tgz",
"integrity": "sha512-qwcshLH9iKnhK0jXoXjPw3G02UhyShT0I+ljC0hMybJEBsra92TYFa47Cp6n1fdvM3+/BTuhsgtzRz0anObicQ==", "integrity": "sha512-uzwZyBVgqz0Wz1KL3aOUaQsxT8TNkzxti4NNTSMrU256qAPqc/n75rB7V73QASapCMpy70mZZTsuPgQYYj4ytQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@puppeteer/browsers": "^1.6.0", "@puppeteer/browsers": "^1.6.0",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.1.0",
"edgedriver": "^5.3.5", "edgedriver": "^5.3.5",
"geckodriver": "^4.2.0", "geckodriver": "^4.2.0",
"get-port": "^7.0.0", "get-port": "^7.0.0",
"got": "^13.0.0", "import-meta-resolve": "^4.0.0",
"import-meta-resolve": "^3.0.0",
"locate-app": "^2.1.0", "locate-app": "^2.1.0",
"safaridriver": "^0.1.0", "safaridriver": "^0.1.0",
"split2": "^4.2.0", "split2": "^4.2.0",
@ -2457,129 +2455,10 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/detect-package-manager": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-3.0.1.tgz",
"integrity": "sha512-qoHDH6+lMcpJPAScE7+5CYj91W0mxZNXTwZPrCqi1KMk+x+AoQScQ2V1QyqTln1rHU5Haq5fikvOGHv+leKD8A==",
"dev": true,
"dependencies": {
"execa": "^5.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/detect-package-manager/node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
"human-signals": "^2.1.0",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.1",
"onetime": "^5.1.2",
"signal-exit": "^3.0.3",
"strip-final-newline": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/detect-package-manager/node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/detect-package-manager/node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true,
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/detect-package-manager/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/detect-package-manager/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/detect-package-manager/node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dev": true,
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/detect-package-manager/node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"dependencies": {
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/detect-package-manager/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"node_modules/detect-package-manager/node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/devtools-protocol": { "node_modules/devtools-protocol": {
"version": "0.0.1213968", "version": "0.0.1233178",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1213968.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1233178.tgz",
"integrity": "sha512-o4n/beY+3CcZwFctYapjGelKptR4AuQT5gXS1Kvgbig+ArwkxK7f8wDVuD1wsoswiJWCwV6OK+Qb7vhNzNmABQ==", "integrity": "sha512-jmMfyaqlzddwmDaSR1AQ+5ek+f7rupZdxKuPdkRcoxrZoF70Idg/4dTgXA08TLPmwAwB54gh49Wm2l/gRM0eUg==",
"dev": true "dev": true
}, },
"node_modules/diff": { "node_modules/diff": {
@ -2927,15 +2806,15 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.54.0", "version": "8.55.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.3", "@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.54.0", "@eslint/js": "8.55.0",
"@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
@ -4008,43 +3887,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/got": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
"integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^5.2.0",
"@szmarczak/http-timer": "^5.0.1",
"cacheable-lookup": "^7.0.0",
"cacheable-request": "^10.2.8",
"decompress-response": "^6.0.0",
"form-data-encoder": "^2.1.2",
"get-stream": "^6.0.1",
"http2-wrapper": "^2.1.10",
"lowercase-keys": "^3.0.0",
"p-cancelable": "^3.0.0",
"responselike": "^3.0.0"
},
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/got/node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -4271,9 +4113,9 @@
} }
}, },
"node_modules/import-meta-resolve": { "node_modules/import-meta-resolve": {
"version": "3.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
"integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
"dev": true, "dev": true,
"funding": { "funding": {
"type": "github", "type": "github",
@ -8616,20 +8458,20 @@
} }
}, },
"node_modules/webdriver": { "node_modules/webdriver": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.24.6.tgz", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.24.12.tgz",
"integrity": "sha512-k5XI2/SHd/14h4ElPQH8EzSUXujZIGbBEi+3dTS2H457KFR5Q8QYfIazDs/YnEdooOp8b6Oe9N7qI99LP8K6bQ==", "integrity": "sha512-03DQIClHoaAqTsmDkxGwo4HwHfkn9LzJ1wfNyUerzKg8DnyXeiT6ILqj6EXLfsvh5zddU2vhYGLFXSerPgkuOQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@wdio/config": "8.24.6", "@wdio/config": "8.24.12",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.23.0", "@wdio/protocols": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"@wdio/utils": "8.24.6", "@wdio/utils": "8.24.12",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.1.0",
"got": "^ 12.6.1", "got": "^12.6.1",
"ky": "^0.33.0", "ky": "^0.33.0",
"ws": "^8.8.0" "ws": "^8.8.0"
}, },
@ -8675,25 +8517,25 @@
} }
}, },
"node_modules/webdriverio": { "node_modules/webdriverio": {
"version": "8.24.6", "version": "8.24.12",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.24.6.tgz", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.24.12.tgz",
"integrity": "sha512-gJMAJiErbXe/oFJbV+H9lXp9GPxnUgHrbtxkG6SCKQlk1zPFho9FZ3fQWl/ty84w5n9ZMhAdnQIfZM9aytxIBQ==", "integrity": "sha512-Ddu0NNRMVkTzRzqvm3m0wt2eLUn+Plz2Cj+1QXDnVpddYJvk9J3elZC2hqNyscEtecQ+h2y3r36OcJqkl9jPag==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/config": "8.24.6", "@wdio/config": "8.24.12",
"@wdio/logger": "8.16.17", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.23.0", "@wdio/protocols": "8.24.12",
"@wdio/repl": "8.23.1", "@wdio/repl": "8.24.12",
"@wdio/types": "8.24.2", "@wdio/types": "8.24.12",
"@wdio/utils": "8.24.6", "@wdio/utils": "8.24.12",
"archiver": "^6.0.0", "archiver": "^6.0.0",
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"css-shorthand-properties": "^1.1.1", "css-shorthand-properties": "^1.1.1",
"css-value": "^0.0.1", "css-value": "^0.0.1",
"devtools-protocol": "^0.0.1213968", "devtools-protocol": "^0.0.1233178",
"grapheme-splitter": "^1.0.2", "grapheme-splitter": "^1.0.2",
"import-meta-resolve": "^3.0.0", "import-meta-resolve": "^4.0.0",
"is-plain-obj": "^4.1.0", "is-plain-obj": "^4.1.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.zip": "^4.2.0", "lodash.zip": "^4.2.0",
@ -8703,7 +8545,7 @@
"resq": "^1.9.1", "resq": "^1.9.1",
"rgb2hex": "0.2.5", "rgb2hex": "0.2.5",
"serialize-error": "^11.0.1", "serialize-error": "^11.0.1",
"webdriver": "8.24.6" "webdriver": "8.24.12"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"

View File

@ -4,13 +4,13 @@
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.1", "@typescript-eslint/parser": "^6.13.2",
"@wdio/cli": "^8.24.6", "@wdio/cli": "^8.24.13",
"@wdio/local-runner": "^8.24.6", "@wdio/local-runner": "^8.24.12",
"@wdio/mocha-framework": "^8.24.6", "@wdio/mocha-framework": "^8.24.12",
"@wdio/spec-reporter": "^8.24.2", "@wdio/spec-reporter": "^8.24.12",
"eslint": "^8.54.0", "eslint": "^8.55.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",

230
web/package-lock.json generated
View File

@ -17,22 +17,22 @@
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.3", "@formatjs/intl-listformat": "^7.5.3",
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2023.10.4-1700591367", "@goauthentik/api": "^2023.10.4-1701882394",
"@lit-labs/context": "^0.4.0", "@lit-labs/context": "^0.4.0",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4", "@lit/localize": "^0.11.4",
"@open-wc/lit-helpers": "^0.6.0", "@open-wc/lit-helpers": "^0.6.0",
"@patternfly/elements": "^2.4.0", "@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.84.0", "@sentry/browser": "^7.85.0",
"@sentry/tracing": "^7.84.0", "@sentry/tracing": "^7.85.0",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^4.4.0", "chart.js": "^4.4.1",
"chartjs-adapter-moment": "^1.0.1", "chartjs-adapter-moment": "^1.0.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0", "construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.33.3", "core-js": "^3.34.0",
"country-flag-icons": "^1.5.9", "country-flag-icons": "^1.5.9",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"lit": "^2.8.0", "lit": "^2.8.0",
@ -70,12 +70,12 @@
"@types/chart.js": "^2.9.41", "@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.7", "@types/grecaptcha": "^3.0.7",
"@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.1", "@typescript-eslint/parser": "^6.13.2",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.54.0", "eslint": "^8.55.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.10.1", "eslint-plugin-lit": "^1.10.1",
@ -2790,9 +2790,9 @@
} }
}, },
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "2.1.3", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
@ -2858,9 +2858,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.54.0", "version": "8.55.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -2944,9 +2944,9 @@
} }
}, },
"node_modules/@goauthentik/api": { "node_modules/@goauthentik/api": {
"version": "2023.10.4-1700591367", "version": "2023.10.4-1701882394",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.4-1700591367.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.4-1701882394.tgz",
"integrity": "sha512-ljC/SHH6ZgGC2qjvuA3gley8sRz9wVzr5FgRGKeqd1mi6G6TfnFYeA7tuuqgQc6WGN2MVMG17FnBraTI77Rl/A==" "integrity": "sha512-rYfJRl4IGQN6OGdWy6f+yMuNRPgmPjkPTI+6V1otR5NwgKrvZeEzy/8Vx1tEkz3CIix8rgZCmu3SmJVsep0ggQ=="
}, },
"node_modules/@hcaptcha/types": { "node_modules/@hcaptcha/types": {
"version": "1.0.3", "version": "1.0.3",
@ -4768,85 +4768,99 @@
"win32" "win32"
] ]
}, },
"node_modules/@sentry-internal/tracing": { "node_modules/@sentry-internal/feedback": {
"version": "7.84.0", "version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.84.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.85.0.tgz",
"integrity": "sha512-y9bGYA0OM6PEREfd+nk4UURZy29tpIw+7vQwpxWfEVs2fqq0/5TBFX/tKFb8AKUI9lVM8v0bcF0bNSCnuPQZHQ==", "integrity": "sha512-MlbIN+N8CWFJBjbqMmARe4+UPo9QRhRar0YoOfmNA2Xqk/EwXcjHWkealosHznXH7tqVbjB25QJpHtDystft/Q==",
"dependencies": { "dependencies": {
"@sentry/core": "7.84.0", "@sentry/core": "7.85.0",
"@sentry/types": "7.84.0", "@sentry/types": "7.85.0",
"@sentry/utils": "7.84.0" "@sentry/utils": "7.85.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry-internal/tracing": {
"version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.85.0.tgz",
"integrity": "sha512-p3YMUwkPCy2su9cm/3+7QYR4RiMI0+07DU1BZtht9NLTzY2O87/yvUbn1v2yHR3vJQTy/+7N0ud9/mPBFznRQQ==",
"dependencies": {
"@sentry/core": "7.85.0",
"@sentry/types": "7.85.0",
"@sentry/utils": "7.85.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "7.84.0", "version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.84.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.85.0.tgz",
"integrity": "sha512-X50TlTKY9WzAnHsYc4FYrCWgm+CdVo0h02ggmodVBUpRLUBjj+cs5Q1plov/v/XeorSwmorNEMUu/n+XZNSsrA==", "integrity": "sha512-x4sH7vTQnZQgy1U7NuN8XwhleAw7YMQitccHeC5m+kpIKGUO7w4Mdvu8rD3dnjmVmZvASpnwocAxy57/vCU6Ww==",
"dependencies": { "dependencies": {
"@sentry-internal/tracing": "7.84.0", "@sentry-internal/feedback": "7.85.0",
"@sentry/core": "7.84.0", "@sentry-internal/tracing": "7.85.0",
"@sentry/replay": "7.84.0", "@sentry/core": "7.85.0",
"@sentry/types": "7.84.0", "@sentry/replay": "7.85.0",
"@sentry/utils": "7.84.0" "@sentry/types": "7.85.0",
"@sentry/utils": "7.85.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "7.84.0", "version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.84.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.85.0.tgz",
"integrity": "sha512-tbuwunbBx2kSex15IHCqHDnrMfIlqPc6w/76fwkGqokz3oh9GSEGlLICwmBWL8AypWimUg13IDtFpD0TJTriWA==", "integrity": "sha512-DFDAc4tWmHN5IWhr7XbHCiyF1Xgb95jz8Uj/JTX9atlgodId1UIbER77qpEmH3eQGid/QBdqrlR98zCixgSbwg==",
"dependencies": { "dependencies": {
"@sentry/types": "7.84.0", "@sentry/types": "7.85.0",
"@sentry/utils": "7.84.0" "@sentry/utils": "7.85.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/replay": { "node_modules/@sentry/replay": {
"version": "7.84.0", "version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.84.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.85.0.tgz",
"integrity": "sha512-c4PxT0ZpvkR9zXNfmAk3ojkm6eZ9+NlDze09RFBOCNo69QwIN90hnvbjXFC1+vRIJsfgo78Zr0ya/Wzb3Rog7Q==", "integrity": "sha512-zVtTKfO+lu5qTwHpETI/oGo8hU3rdKHr3CdI1vRLw+d60PcAa/pWVlXsQeLRTw8PFwE358gHcpFZezj/11afew==",
"dependencies": { "dependencies": {
"@sentry-internal/tracing": "7.84.0", "@sentry-internal/tracing": "7.85.0",
"@sentry/core": "7.84.0", "@sentry/core": "7.85.0",
"@sentry/types": "7.84.0", "@sentry/types": "7.85.0",
"@sentry/utils": "7.84.0" "@sentry/utils": "7.85.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@sentry/tracing": { "node_modules/@sentry/tracing": {
"version": "7.84.0", "version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.84.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.85.0.tgz",
"integrity": "sha512-NhBX28vUmCu/5avyGKX6B4UTm4MTOfbdg9ZzCnS7hPuWDfEAUIVHZVryi2q8bqp2DNGJvS9qIq/TSf39JIpdJg==", "integrity": "sha512-L3bpqiM+zu5f3o6zh6hx3xEzVENyhrkuMlpUOyDo0mUytqp763HqF1xz+R+trzze7R5VWrxJaRPARsCKlXu4Ig==",
"dependencies": { "dependencies": {
"@sentry-internal/tracing": "7.84.0" "@sentry-internal/tracing": "7.85.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/types": { "node_modules/@sentry/types": {
"version": "7.84.0", "version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.84.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.85.0.tgz",
"integrity": "sha512-VqGLIF3JOUrk7yIXjLXJvAORkZL1e3dDX0Q1okRehwyt/5CRE+mdUTeJZkBo9P9mBwgMyvtwklzOGGrzjb4eMA==", "integrity": "sha512-R5jR4XkK5tBU2jDiPdSVqzkmjYRr666bcGaFGUHB/xDQCjPsjk+pEmCCL+vpuWoaZmQJUE1hVU7rgnVX81w8zg==",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/utils": { "node_modules/@sentry/utils": {
"version": "7.84.0", "version": "7.85.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.84.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.85.0.tgz",
"integrity": "sha512-qdUVuxnRBvaf05AU+28R+xYtZmi/Ymf8os3Njq9g4XuA+QEkZLbzmIpRK5W9Ja7vUtjOeg29Xgg43A8znde9LQ==", "integrity": "sha512-JZ7seNOLvhjAQ8GeB3GYknPQJkuhF88xAYOaESZP3xPOWBMFUN+IO4RqjMqMLFDniOwsVQS7GB/MfP+hxufieg==",
"dependencies": { "dependencies": {
"@sentry/types": "7.84.0" "@sentry/types": "7.85.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -10526,16 +10540,16 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.2.tgz",
"integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", "integrity": "sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.5.1", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.13.1", "@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/type-utils": "6.13.1", "@typescript-eslint/type-utils": "6.13.2",
"@typescript-eslint/utils": "6.13.1", "@typescript-eslint/utils": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1", "@typescript-eslint/visitor-keys": "6.13.2",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.4", "ignore": "^5.2.4",
@ -10594,15 +10608,15 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.2.tgz",
"integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", "integrity": "sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "6.13.1", "@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/typescript-estree": "6.13.1", "@typescript-eslint/typescript-estree": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1", "@typescript-eslint/visitor-keys": "6.13.2",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -10622,13 +10636,13 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", "integrity": "sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1" "@typescript-eslint/visitor-keys": "6.13.2"
}, },
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -10639,13 +10653,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.2.tgz",
"integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", "integrity": "sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "6.13.1", "@typescript-eslint/typescript-estree": "6.13.2",
"@typescript-eslint/utils": "6.13.1", "@typescript-eslint/utils": "6.13.2",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
@ -10666,9 +10680,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", "integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -10679,13 +10693,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", "integrity": "sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.1", "@typescript-eslint/visitor-keys": "6.13.2",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -10739,17 +10753,17 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.2.tgz",
"integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", "integrity": "sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.13.1", "@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"@typescript-eslint/typescript-estree": "6.13.1", "@typescript-eslint/typescript-estree": "6.13.2",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
@ -10797,12 +10811,12 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.1", "version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", "integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.13.1", "@typescript-eslint/types": "6.13.2",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
@ -11687,9 +11701,9 @@
} }
}, },
"node_modules/chart.js": { "node_modules/chart.js": {
"version": "4.4.0", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
"integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
"dependencies": { "dependencies": {
"@kurkle/color": "^0.3.0" "@kurkle/color": "^0.3.0"
}, },
@ -12092,9 +12106,9 @@
"dev": true "dev": true
}, },
"node_modules/core-js": { "node_modules/core-js": {
"version": "3.33.3", "version": "3.34.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz",
"integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==",
"hasInstallScript": true, "hasInstallScript": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -13362,15 +13376,15 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.54.0", "version": "8.55.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.3", "@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.54.0", "@eslint/js": "8.55.0",
"@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",

View File

@ -38,22 +38,22 @@
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.3", "@formatjs/intl-listformat": "^7.5.3",
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2023.10.4-1700591367", "@goauthentik/api": "^2023.10.4-1701882394",
"@lit-labs/context": "^0.4.0", "@lit-labs/context": "^0.4.0",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4", "@lit/localize": "^0.11.4",
"@open-wc/lit-helpers": "^0.6.0", "@open-wc/lit-helpers": "^0.6.0",
"@patternfly/elements": "^2.4.0", "@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.84.0", "@sentry/browser": "^7.85.0",
"@sentry/tracing": "^7.84.0", "@sentry/tracing": "^7.85.0",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^4.4.0", "chart.js": "^4.4.1",
"chartjs-adapter-moment": "^1.0.1", "chartjs-adapter-moment": "^1.0.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0", "construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.33.3", "core-js": "^3.34.0",
"country-flag-icons": "^1.5.9", "country-flag-icons": "^1.5.9",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"lit": "^2.8.0", "lit": "^2.8.0",
@ -91,12 +91,12 @@
"@types/chart.js": "^2.9.41", "@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.7", "@types/grecaptcha": "^3.0.7",
"@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.1", "@typescript-eslint/parser": "^6.13.2",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.54.0", "eslint": "^8.55.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.10.1", "eslint-plugin-lit": "^1.10.1",

View File

@ -89,17 +89,15 @@ export class ApplicationListPage extends TablePage<Application> {
]; ];
} }
renderSectionBefore(): TemplateResult {
return html`<ak-application-wizard-hint></ak-application-wizard-hint>`;
}
renderSidebarAfter(): TemplateResult { renderSidebarAfter(): TemplateResult {
// Rendering the wizard with .open here, as if we set the attribute in // Rendering the wizard with .open here, as if we set the attribute in
// renderObjectCreate() it'll open two wizards, since that function gets called twice // renderObjectCreate() it'll open two wizards, since that function gets called twice
/* Re-enable the wizard later: return html`<div class="pf-c-sidebar__panel pf-m-width-25">
<ak-application-wizard
.open=${getURLParam("createWizard", false)}
.showButton=${false}
></ak-application-wizard>*/
return html` <div class="pf-c-sidebar__panel pf-m-width-25">
<div class="pf-c-card"> <div class="pf-c-card">
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<ak-markdown .md=${MDApplication}></ak-markdown> <ak-markdown .md=${MDApplication}></ak-markdown>

View File

@ -1,5 +1,7 @@
import { WizardPanel } from "@goauthentik/components/ak-wizard-main/types"; import { WizardPanel } from "@goauthentik/components/ak-wizard-main/types";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { KeyUnknown, serializeForm } from "@goauthentik/elements/forms/Form";
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
import { consume } from "@lit-labs/context"; import { consume } from "@lit-labs/context";
@ -10,6 +12,15 @@ import { styles as AwadStyles } from "./BasePanel.css";
import { applicationWizardContext } from "./ContextIdentity"; import { applicationWizardContext } from "./ContextIdentity";
import type { ApplicationWizardState, ApplicationWizardStateUpdate } from "./types"; import type { ApplicationWizardState, ApplicationWizardStateUpdate } from "./types";
/**
* Application Wizard Base Panel
*
* All of the displays in our system inherit from this object, which supplies the basic CSS for all
* the inputs we display, as well as the values and validity state for the form currently being
* displayed.
*
*/
export class ApplicationWizardPageBase export class ApplicationWizardPageBase
extends CustomEmitterElement(AKElement) extends CustomEmitterElement(AKElement)
implements WizardPanel implements WizardPanel
@ -18,15 +29,41 @@ export class ApplicationWizardPageBase
return AwadStyles; return AwadStyles;
} }
@query("form")
form!: HTMLFormElement;
rendered = false;
@consume({ context: applicationWizardContext }) @consume({ context: applicationWizardContext })
public wizard!: ApplicationWizardState; public wizard!: ApplicationWizardState;
// This used to be more complex; now it just establishes the event name. @query("form")
form!: HTMLFormElement;
/**
* Provide access to the values on the current form. Child implementations use this to craft the
* update that will be sent using `dispatchWizardUpdate` below.
*/
get formValues(): KeyUnknown | undefined {
const elements = [
...Array.from(
this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
),
...Array.from(this.form.querySelectorAll<HTMLElement>("[data-ak-control=true]")),
];
return serializeForm(elements as unknown as NodeListOf<HorizontalFormElement>);
}
/**
* Provide access to the validity of the current form. Child implementations use this to craft
* the update that will be sent using `dispatchWizardUpdate` below.
*/
get valid() {
return this.form.checkValidity();
}
rendered = false;
/**
* Provide a single source of truth for the token used to notify the orchestrator that an event
* happens. The token `ak-wizard-update` is used by the Wizard framework's reactive controller
* to route "data on the current step has changed" events to the orchestrator.
*/
dispatchWizardUpdate(update: ApplicationWizardStateUpdate) { dispatchWizardUpdate(update: ApplicationWizardStateUpdate) {
this.dispatchCustomEvent("ak-wizard-update", update); this.dispatchCustomEvent("ak-wizard-update", update);
} }

View File

@ -5,5 +5,3 @@ import { ApplicationWizardState } from "./types";
export const applicationWizardContext = createContext<ApplicationWizardState>( export const applicationWizardContext = createContext<ApplicationWizardState>(
Symbol("ak-application-wizard-state-context"), Symbol("ak-application-wizard-state-context"),
); );
export default applicationWizardContext;

View File

@ -1,4 +1,3 @@
import { merge } from "@goauthentik/common/merge";
import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard"; import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard";
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
@ -6,7 +5,7 @@ import { ContextProvider } from "@lit-labs/context";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { customElement, state } from "lit/decorators.js"; import { customElement, state } from "lit/decorators.js";
import applicationWizardContext from "./ContextIdentity"; import { applicationWizardContext } from "./ContextIdentity";
import { newSteps } from "./steps"; import { newSteps } from "./steps";
import { import {
ApplicationStep, ApplicationStep,
@ -15,10 +14,11 @@ import {
OneOfProvider, OneOfProvider,
} from "./types"; } from "./types";
const freshWizardState = () => ({ const freshWizardState = (): ApplicationWizardState => ({
providerModel: "", providerModel: "",
app: {}, app: {},
provider: {}, provider: {},
errors: {},
}); });
@customElement("ak-application-wizard") @customElement("ak-application-wizard")
@ -56,28 +56,6 @@ export class ApplicationWizard extends CustomListenerElement(
*/ */
providerCache: Map<string, OneOfProvider> = new Map(); providerCache: Map<string, OneOfProvider> = new Map();
maybeProviderSwap(providerModel: string | undefined): boolean {
if (
providerModel === undefined ||
typeof providerModel !== "string" ||
providerModel === this.wizardState.providerModel
) {
return false;
}
this.providerCache.set(this.wizardState.providerModel, this.wizardState.provider);
const prevProvider = this.providerCache.get(providerModel);
this.wizardState.provider = prevProvider ?? {
name: `Provider for ${this.wizardState.app.name}`,
};
const method = this.steps.find(({ id }) => id === "provider-details");
if (!method) {
throw new Error("Could not find Authentication Method page?");
}
method.disabled = false;
return true;
}
// And this is where all the special cases go... // And this is where all the special cases go...
handleUpdate(detail: ApplicationWizardStateUpdate) { handleUpdate(detail: ApplicationWizardStateUpdate) {
if (detail.status === "submitted") { if (detail.status === "submitted") {
@ -87,17 +65,26 @@ export class ApplicationWizard extends CustomListenerElement(
} }
this.step.valid = this.step.valid || detail.status === "valid"; this.step.valid = this.step.valid || detail.status === "valid";
const update = detail.update; const update = detail.update;
if (!update) { if (!update) {
return; return;
} }
if (this.maybeProviderSwap(update.providerModel)) { // When the providerModel enum changes, retrieve the customer's prior work for *this* wizard
this.requestUpdate(); // session (and only this wizard session) or provide an empty model with a default provider
// name.
if (update.providerModel && update.providerModel !== this.wizardState.providerModel) {
const requestedProvider = this.providerCache.get(update.providerModel) ?? {
name: `Provider for ${this.wizardState.app.name}`,
};
if (this.wizardState.providerModel) {
this.providerCache.set(this.wizardState.providerModel, this.wizardState.provider);
}
update.provider = requestedProvider;
} }
this.wizardState = merge(this.wizardState, update) as ApplicationWizardState; this.wizardState = update as ApplicationWizardState;
this.wizardStateProvider.setValue(this.wizardState); this.wizardStateProvider.setValue(this.wizardState);
this.requestUpdate(); this.requestUpdate();
} }

View File

@ -0,0 +1,30 @@
import { AKElement } from "@goauthentik/elements/Base";
import { css, html } from "lit";
import { customElement } from "lit/decorators.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
@customElement("ak-wizard-title")
export class AkWizardTitle extends AKElement {
static get styles() {
return [
PFContent,
PFTitle,
css`
.ak-bottom-spacing {
padding-bottom: var(--pf-global--spacer--lg);
}
`,
];
}
render() {
return html`<div class="ak-bottom-spacing pf-c-content">
<h3><slot></slot></h3>
</div>`;
}
}
export default AkWizardTitle;

View File

@ -5,7 +5,6 @@ import "@goauthentik/components/ak-slug-input";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -17,28 +16,20 @@ import BasePanel from "../BasePanel";
@customElement("ak-application-wizard-application-details") @customElement("ak-application-wizard-application-details")
export class ApplicationWizardApplicationDetails extends BasePanel { export class ApplicationWizardApplicationDetails extends BasePanel {
handleChange(ev: Event) { handleChange(_ev: Event) {
if (!ev.target) { const formValues = this.formValues;
console.warn(`Received event with no target: ${ev}`); if (!formValues) {
return; throw new Error("No application values on form?");
} }
const target = ev.target as HTMLInputElement;
const value = target.type === "checkbox" ? target.checked : target.value;
this.dispatchWizardUpdate({ this.dispatchWizardUpdate({
update: { update: {
app: { ...this.wizard,
[target.name]: value, app: formValues,
},
}, },
status: this.form.checkValidity() ? "valid" : "invalid", status: this.valid ? "valid" : "invalid",
}); });
} }
validator() {
return this.form.reportValidity();
}
render(): TemplateResult { render(): TemplateResult {
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input <ak-text-input
@ -48,6 +39,7 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
required required
help=${msg("Application's display Name.")} help=${msg("Application's display Name.")}
id="ak-application-wizard-details-name" id="ak-application-wizard-details-name"
.errorMessages=${this.wizard.errors.app?.name ?? []}
></ak-text-input> ></ak-text-input>
<ak-slug-input <ak-slug-input
name="slug" name="slug"
@ -56,11 +48,13 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
source="#ak-application-wizard-details-name" source="#ak-application-wizard-details-name"
required required
help=${msg("Internal application name used in URLs.")} help=${msg("Internal application name used in URLs.")}
.errorMessages=${this.wizard.errors.app?.slug ?? []}
></ak-slug-input> ></ak-slug-input>
<ak-text-input <ak-text-input
name="group" name="group"
value=${ifDefined(this.wizard.app?.group)} value=${ifDefined(this.wizard.app?.group)}
label=${msg("Group")} label=${msg("Group")}
.errorMessages=${this.wizard.errors.app?.group ?? []}
help=${msg( help=${msg(
"Optionally enter a group name. Applications with identical groups are shown grouped together.", "Optionally enter a group name. Applications with identical groups are shown grouped together.",
)} )}
@ -71,6 +65,7 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
name="policyEngineMode" name="policyEngineMode"
.options=${policyOptions} .options=${policyOptions}
.value=${this.wizard.app?.policyEngineMode} .value=${this.wizard.app?.policyEngineMode}
.errorMessages=${this.wizard.errors.app?.policyEngineMode ?? []}
></ak-radio-input> ></ak-radio-input>
<ak-form-group aria-label="UI Settings"> <ak-form-group aria-label="UI Settings">
<span slot="header"> ${msg("UI Settings")} </span> <span slot="header"> ${msg("UI Settings")} </span>
@ -82,6 +77,7 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
help=${msg( help=${msg(
"If left empty, authentik will try to extract the launch URL based on the selected provider.", "If left empty, authentik will try to extract the launch URL based on the selected provider.",
)} )}
.errorMessages=${this.wizard.errors.app?.metaLaunchUrl ?? []}
></ak-text-input> ></ak-text-input>
<ak-switch-input <ak-switch-input
name="openInNewTab" name="openInNewTab"

View File

@ -19,13 +19,17 @@ type ProviderRenderer = () => TemplateResult;
type ModelConverter = (provider: OneOfProvider) => ModelRequest; type ModelConverter = (provider: OneOfProvider) => ModelRequest;
/**
* There's an internal key and an API key because "Proxy" has three different subtypes.
*/
// prettier-ignore
type ProviderType = [ type ProviderType = [
string, string, // internal key used by the wizard to distinguish between providers
string, string, // Name of the provider
string, string, // Description
ProviderRenderer, ProviderRenderer, // Function that returns the provider's wizard panel as a TemplateResult
ProviderModelEnumType, ProviderModelEnumType, // key used by the API to distinguish between providers
ModelConverter, ModelConverter, // Handler that takes a generic provider and returns one specifically typed to its panel
]; ];
export type LocalTypeCreate = TypeCreate & { export type LocalTypeCreate = TypeCreate & {

View File

@ -25,19 +25,15 @@ export class ApplicationWizardAuthenticationMethodChoice extends BasePanel {
handleChoice(ev: InputEvent) { handleChoice(ev: InputEvent) {
const target = ev.target as HTMLInputElement; const target = ev.target as HTMLInputElement;
this.dispatchWizardUpdate({ this.dispatchWizardUpdate({
update: { providerModel: target.value }, update: {
status: this.validator() ? "valid" : "invalid", ...this.wizard,
providerModel: target.value,
errors: {},
},
status: this.valid ? "valid" : "invalid",
}); });
} }
validator() {
const radios = Array.from(this.form.querySelectorAll('input[type="radio"]'));
const chosen = radios.find(
(radio: Element) => radio instanceof HTMLInputElement && radio.checked,
);
return !!chosen;
}
renderProvider(type: LocalTypeCreate) { renderProvider(type: LocalTypeCreate) {
const method = this.wizard.providerModel; const method = this.wizard.providerModel;

View File

@ -18,12 +18,14 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css"; import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
import { import {
ApplicationRequest, type ApplicationRequest,
CoreApi, CoreApi,
TransactionApplicationRequest, type ModelRequest,
TransactionApplicationResponse, type TransactionApplicationRequest,
type TransactionApplicationResponse,
ValidationError,
ValidationErrorFromJSON,
} from "@goauthentik/api"; } from "@goauthentik/api";
import type { ModelRequest } from "@goauthentik/api";
import BasePanel from "../BasePanel"; import BasePanel from "../BasePanel";
import providerModelsList from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices"; import providerModelsList from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
@ -88,7 +90,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
commitState: State = idleState; commitState: State = idleState;
@state() @state()
errors: string[] = []; errors?: ValidationError;
response?: TransactionApplicationResponse; response?: TransactionApplicationResponse;
@ -121,27 +123,10 @@ export class ApplicationWizardCommitApplication extends BasePanel {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
decodeErrors(body: Record<string, any>) {
const spaceify = (src: Record<string, string>) =>
Object.values(src).map((msg) => `\u00a0\u00a0\u00a0\u00a0${msg}`);
let errs: string[] = [];
if (body["app"] !== undefined) {
errs = [...errs, msg("In the Application:"), ...spaceify(body["app"])];
}
if (body["provider"] !== undefined) {
errs = [...errs, msg("In the Provider:"), ...spaceify(body["provider"])];
}
console.log(body, errs);
return errs;
}
async send( async send(
data: TransactionApplicationRequest, data: TransactionApplicationRequest,
): Promise<TransactionApplicationResponse | void> { ): Promise<TransactionApplicationResponse | void> {
this.errors = []; this.errors = undefined;
new CoreApi(DEFAULT_CONFIG) new CoreApi(DEFAULT_CONFIG)
.coreTransactionalApplicationsUpdate({ .coreTransactionalApplicationsUpdate({
transactionApplicationRequest: data, transactionApplicationRequest: data,
@ -153,18 +138,57 @@ export class ApplicationWizardCommitApplication extends BasePanel {
this.commitState = successState; this.commitState = successState;
}) })
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((resolution: any) => { .catch(async (resolution: any) => {
resolution.response.json().then( const errors = (this.errors = ValidationErrorFromJSON(
// eslint-disable-next-line @typescript-eslint/no-explicit-any await resolution.response.json(),
(body: Record<string, any>) => { ));
this.errors = this.decodeErrors(body); this.dispatchWizardUpdate({
update: {
...this.wizard,
errors,
}, },
); status: "failed",
});
this.commitState = errorState; this.commitState = errorState;
}); });
} }
render(): TemplateResult { renderErrors(errors?: ValidationError) {
if (!errors) {
return nothing;
}
const navTo = (step: number) => () =>
this.dispatchCustomEvent("ak-wizard-nav", {
command: "goto",
step,
});
if (errors.app) {
return html`<p>${msg("There was an error in the application.")}</p>
<p><a @click=${navTo(0)}>${msg("Review the application.")}</a></p>`;
}
if (errors.provider) {
return html`<p>${msg("There was an error in the provider.")}</p>
<p><a @click=${navTo(2)}>${msg("Review the provider.")}</a></p>`;
}
if (errors.detail) {
return html`<p>${msg("There was an error")}: ${errors.detail}</p>`;
}
if ((errors?.nonFieldErrors ?? []).length > 0) {
return html`<p>$(msg("There was an error")}:</p>
<ul>
${(errors.nonFieldErrors ?? []).map((e: string) => html`<li>${e}</li>`)}
</ul>`;
}
return html`<p>
${msg(
"There was an error creating the application, but no error message was sent. Please review the server logs.",
)}
</p>`;
}
render() {
const icon = classMap( const icon = classMap(
this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {}), this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {}),
); );
@ -184,13 +208,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
> >
${this.commitState.label} ${this.commitState.label}
</h1> </h1>
${this.errors.length > 0 ${this.renderErrors(this.errors)}
? html`<ul>
${this.errors.map(
(msg) => html`<li><code>${msg}</code></li>`,
)}
</ul>`
: nothing}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,26 +1,19 @@
import BasePanel from "../BasePanel"; import BasePanel from "../BasePanel";
export class ApplicationWizardProviderPageBase extends BasePanel { export class ApplicationWizardProviderPageBase extends BasePanel {
handleChange(ev: InputEvent) { handleChange(_ev: InputEvent) {
if (!ev.target) { const formValues = this.formValues;
console.warn(`Received event with no target: ${ev}`); if (!formValues) {
return; throw new Error("No provider values on form?");
} }
const target = ev.target as HTMLInputElement;
const value = target.type === "checkbox" ? target.checked : target.value;
this.dispatchWizardUpdate({ this.dispatchWizardUpdate({
update: { update: {
provider: { ...this.wizard,
[target.name]: value, provider: formValues,
},
}, },
status: this.form.checkValidity() ? "valid" : "invalid", status: this.valid ? "valid" : "invalid",
}); });
} }
validator() {
return this.form.reportValidity();
}
} }
export default ApplicationWizardProviderPageBase; export default ApplicationWizardProviderPageBase;

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search"; import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
@ -34,112 +35,132 @@ import {
export class ApplicationWizardApplicationDetails extends BaseProviderPanel { export class ApplicationWizardApplicationDetails extends BaseProviderPanel {
render() { render() {
const provider = this.wizard.provider as LDAPProvider | undefined; const provider = this.wizard.provider as LDAPProvider | undefined;
const errors = this.wizard.errors.provider;
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> return html` <ak-wizard-title>${msg("Configure LDAP Provider")}</ak-wizard-title>
<ak-text-input <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
name="name" <ak-text-input
value=${ifDefined(provider?.name)} name="name"
label=${msg("Name")} value=${ifDefined(provider?.name)}
required label=${msg("Name")}
help=${msg("Method's display Name.")} .errorMessages=${errors?.name ?? []}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Bind flow")}
?required=${true}
name="authorizationFlow"
>
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.brandFlow=${rootInterface()?.brand?.flowAuthentication}
required required
></ak-branded-flow-search> help=${msg("Method's display Name.")}
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p> ></ak-text-input>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Search group")} name="searchGroup"> <ak-form-element-horizontal
<ak-core-group-search label=${msg("Bind flow")}
?required=${true}
name="authorizationFlow"
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.brandFlow=${rootInterface()?.brand?.flowAuthentication}
required
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used for users to authenticate.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Search group")}
name="searchGroup" name="searchGroup"
group=${ifDefined(provider?.searchGroup ?? nothing)} .errorMessages=${errors?.searchGroup ?? []}
></ak-core-group-search> >
<p class="pf-c-form__helper-text">${groupHelp}</p> <ak-core-group-search
</ak-form-element-horizontal> name="searchGroup"
group=${ifDefined(provider?.searchGroup ?? nothing)}
></ak-core-group-search>
<p class="pf-c-form__helper-text">${groupHelp}</p>
</ak-form-element-horizontal>
<ak-radio-input <ak-radio-input
label=${msg("Bind mode")} label=${msg("Bind mode")}
name="bindMode" name="bindMode"
.options=${bindModeOptions} .options=${bindModeOptions}
.value=${provider?.bindMode} .value=${provider?.bindMode}
help=${msg("Configure how the outpost authenticates requests.")} help=${msg("Configure how the outpost authenticates requests.")}
> >
</ak-radio-input> </ak-radio-input>
<ak-radio-input <ak-radio-input
label=${msg("Search mode")} label=${msg("Search mode")}
name="searchMode" name="searchMode"
.options=${searchModeOptions} .options=${searchModeOptions}
.value=${provider?.searchMode} .value=${provider?.searchMode}
help=${msg("Configure how the outpost queries the core authentik server's users.")} help=${msg(
> "Configure how the outpost queries the core authentik server's users.",
</ak-radio-input> )}
>
</ak-radio-input>
<ak-switch-input <ak-switch-input
name="openInNewTab" name="openInNewTab"
label=${msg("Code-based MFA Support")} label=${msg("Code-based MFA Support")}
?checked=${provider?.mfaSupport} ?checked=${provider?.mfaSupport ?? true}
help=${mfaSupportHelp} help=${mfaSupportHelp}
> >
</ak-switch-input> </ak-switch-input>
<ak-form-group .expanded=${true}> <ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span> <span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-text-input <ak-text-input
name="baseDn" name="baseDn"
label=${msg("Base DN")} label=${msg("Base DN")}
required required
value="${first(provider?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}" value="${first(provider?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
help=${msg( .errorMessages=${errors?.baseDn ?? []}
"LDAP DN under which bind requests and search requests can be made.", help=${msg(
)} "LDAP DN under which bind requests and search requests can be made.",
> )}
</ak-text-input>
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.certificate ?? nothing)}
name="certificate"
> >
</ak-crypto-certificate-search> </ak-text-input>
<p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
</ak-form-element-horizontal>
<ak-text-input <ak-form-element-horizontal
label=${msg("TLS Server name")} label=${msg("Certificate")}
name="tlsServerName" name="certificate"
value="${first(provider?.tlsServerName, "")}" .errorMessages=${errors?.certificate ?? []}
help=${tlsServerNameHelp} >
></ak-text-input> <ak-crypto-certificate-search
certificate=${ifDefined(provider?.certificate ?? nothing)}
name="certificate"
>
</ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
</ak-form-element-horizontal>
<ak-number-input <ak-text-input
label=${msg("UID start number")} label=${msg("TLS Server name")}
required name="tlsServerName"
name="uidStartNumber" value="${first(provider?.tlsServerName, "")}"
value="${first(provider?.uidStartNumber, 2000)}" .errorMessages=${errors?.tlsServerName ?? []}
help=${uidStartNumberHelp} help=${tlsServerNameHelp}
></ak-number-input> ></ak-text-input>
<ak-number-input <ak-number-input
label=${msg("GID start number")} label=${msg("UID start number")}
required required
name="gidStartNumber" name="uidStartNumber"
value="${first(provider?.gidStartNumber, 4000)}" value="${first(provider?.uidStartNumber, 2000)}"
help=${gidStartNumberHelp} .errorMessages=${errors?.uidStartNumber ?? []}
></ak-number-input> help=${uidStartNumberHelp}
</div> ></ak-number-input>
</ak-form-group>
</form>`; <ak-number-input
label=${msg("GID start number")}
required
name="gidStartNumber"
value="${first(provider?.gidStartNumber, 4000)}"
.errorMessages=${errors?.gidStartNumber ?? []}
help=${gidStartNumberHelp}
></ak-number-input>
</div>
</ak-form-group>
</form>`;
} }
} }

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { import {
@ -27,10 +28,10 @@ import {
PropertymappingsApi, PropertymappingsApi,
SourcesApi, SourcesApi,
} from "@goauthentik/api"; } from "@goauthentik/api";
import type { import {
OAuth2Provider, type OAuth2Provider,
PaginatedOAuthSourceList, type PaginatedOAuthSourceList,
PaginatedScopeMappingList, type PaginatedScopeMappingList,
} from "@goauthentik/api"; } from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel"; import BaseProviderPanel from "../BaseProviderPanel";
@ -38,7 +39,7 @@ import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-oauth") @customElement("ak-application-wizard-authentication-by-oauth")
export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel { export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
@state() @state()
showClientSecret = false; showClientSecret = true;
@state() @state()
propertyMappings?: PaginatedScopeMappingList; propertyMappings?: PaginatedScopeMappingList;
@ -68,234 +69,254 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
render() { render() {
const provider = this.wizard.provider as OAuth2Provider | undefined; const provider = this.wizard.provider as OAuth2Provider | undefined;
const errors = this.wizard.errors.provider;
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> return html`<ak-wizard-title>${msg("Configure OAuth2/OpenId Provider")}</ak-wizard-title>
<ak-text-input <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
name="name" <ak-text-input
label=${msg("Name")} name="name"
value=${ifDefined(provider?.name)} label=${msg("Name")}
required value=${ifDefined(provider?.name)}
></ak-text-input> .errorMessages=${errors?.name ?? []}
<ak-form-element-horizontal
name="authenticationFlow"
label=${msg("Authentication flow")}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authenticationFlow}
required required
></ak-flow-search> ></ak-text-input>
<p class="pf-c-form__helper-text">
${msg("Flow used when a user access this provider and is not authenticated.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
name="authorizationFlow" name="authenticationFlow"
label=${msg("Authorization flow")} label=${msg("Authentication flow")}
?required=${true} .errorMessages=${errors?.authenticationFlow ?? []}
> >
<ak-flow-search <ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization} flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow} .currentFlow=${provider?.authenticationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-radio-input
name="clientType"
label=${msg("Client type")}
.value=${provider?.clientType}
required required
@change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => { ></ak-flow-search>
this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public; <p class="pf-c-form__helper-text">
}} ${msg(
.options=${clientTypeOptions} "Flow used when a user access this provider and is not authenticated.",
> )}
</ak-radio-input> </p>
</ak-form-element-horizontal>
<ak-text-input <ak-form-element-horizontal
name="clientId" name="authorizationFlow"
label=${msg("Client ID")} label=${msg("Authorization flow")}
value="${first( .errorMessages=${errors?.authorizationFlow ?? []}
provider?.clientId, ?required=${true}
randomString(40, ascii_letters + digits), >
)}" <ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required required
> ></ak-flow-search>
</ak-text-input> <p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-text-input <ak-form-group .expanded=${true}>
name="clientSecret" <span slot="header"> ${msg("Protocol settings")} </span>
label=${msg("Client Secret")} <div slot="body" class="pf-c-form">
value="${first( <ak-radio-input
provider?.clientSecret, name="clientType"
randomString(128, ascii_letters + digits), label=${msg("Client type")}
)}" .value=${provider?.clientType}
?hidden=${!this.showClientSecret} required
> @change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => {
</ak-text-input> this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public;
}}
<ak-textarea-input .options=${clientTypeOptions}
name="redirectUris"
label=${msg("Redirect URIs/Origins (RegEx)")}
.value=${provider?.redirectUris}
.bighelp=${redirectUriHelp}
>
</ak-textarea-input>
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.signingKey ?? nothing)}
name="certificate"
singleton
> >
</ak-crypto-certificate-search> </ak-radio-input>
<p class="pf-c-form__helper-text">${msg("Key used to sign the tokens.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group> <ak-text-input
<span slot="header"> ${msg("Advanced protocol settings")} </span> name="clientId"
<div slot="body" class="pf-c-form"> label=${msg("Client ID")}
<ak-text-input value=${provider?.clientId ?? randomString(40, ascii_letters + digits)}
name="accessCodeValidity" .errorMessages=${errors?.clientId ?? []}
label=${msg("Access code validity")} required
required >
value="${first(provider?.accessCodeValidity, "minutes=1")}" </ak-text-input>
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Configure how long access codes are valid for.")} <ak-text-input
name="clientSecret"
label=${msg("Client Secret")}
value=${provider?.clientSecret ??
randomString(128, ascii_letters + digits)}
.errorMessages=${errors?.clientSecret ?? []}
?hidden=${!this.showClientSecret}
>
</ak-text-input>
<ak-textarea-input
name="redirectUris"
label=${msg("Redirect URIs/Origins (RegEx)")}
.value=${provider?.redirectUris}
.errorMessages=${errors?.redirectUriHelp ?? []}
.bighelp=${redirectUriHelp}
>
</ak-textarea-input>
<ak-form-element-horizontal
label=${msg("Signing Key")}
name="signingKey"
.errorMessages=${errors?.signingKey ?? []}
>
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.signingKey ?? nothing)}
name="certificate"
singleton
>
</ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">
${msg("Key used to sign the tokens.")}
</p> </p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`} </ak-form-element-horizontal>
> </div>
</ak-text-input> </ak-form-group>
<ak-text-input <ak-form-group>
name="accessTokenValidity" <span slot="header"> ${msg("Advanced protocol settings")} </span>
label=${msg("Access Token validity")} <div slot="body" class="pf-c-form">
value="${first(provider?.accessTokenValidity, "minutes=5")}" <ak-text-input
required name="accessCodeValidity"
.bighelp=${html` <p class="pf-c-form__helper-text"> label=${msg("Access code validity")}
${msg("Configure how long access tokens are valid for.")} required
value="${first(provider?.accessCodeValidity, "minutes=1")}"
.errorMessages=${errors?.accessCodeValidity ?? []}
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Configure how long access codes are valid for.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-text-input
name="accessTokenValidity"
label=${msg("Access Token validity")}
value="${first(provider?.accessTokenValidity, "minutes=5")}"
required
.errorMessages=${errors?.accessTokenValidity ?? []}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg("Configure how long access tokens are valid for.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-text-input
name="refreshTokenValidity"
label=${msg("Refresh Token validity")}
value="${first(provider?.refreshTokenValidity, "days=30")}"
.errorMessages=${errors?.refreshTokenValidity ?? []}
?required=${true}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg("Configure how long refresh tokens are valid for.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-form-element-horizontal
label=${msg("Scopes")}
name="propertyMappings"
.errorMessages=${errors?.propertyMappings ?? []}
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((scope) => {
let selected = false;
if (!provider?.propertyMappings) {
selected =
scope.managed?.startsWith(
"goauthentik.io/providers/oauth2/scope-",
) || false;
} else {
selected = Array.from(provider?.propertyMappings).some(
(su) => {
return su == scope.pk;
},
);
}
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg(
"Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
)}
</p> </p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`} <p class="pf-c-form__helper-text">
> ${msg("Hold control/command to select multiple items.")}
</ak-text-input>
<ak-text-input
name="refreshTokenValidity"
label=${msg("Refresh Token validity")}
value="${first(provider?.refreshTokenValidity, "days=30")}"
?required=${true}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg("Configure how long refresh tokens are valid for.")}
</p> </p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`} </ak-form-element-horizontal>
>
</ak-text-input>
<ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings"> <ak-radio-input
<select class="pf-c-form-control" multiple> name="subMode"
${this.propertyMappings?.results.map((scope) => { label=${msg("Subject mode")}
let selected = false; required
if (!provider?.propertyMappings) { .options=${subjectModeOptions}
selected = .value=${provider?.subMode}
scope.managed?.startsWith( help=${msg(
"goauthentik.io/providers/oauth2/scope-", "Configure what data should be used as unique User Identifier. For most cases, the default should be fine.",
) || false; )}
} else { >
selected = Array.from(provider?.propertyMappings).some((su) => { </ak-radio-input>
return su == scope.pk; <ak-switch-input
name="includeClaimsInIdToken"
label=${msg("Include claims in id_token")}
?checked=${first(provider?.includeClaimsInIdToken, true)}
help=${msg(
"Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
)}
></ak-switch-input>
<ak-radio-input
name="issuerMode"
label=${msg("Issuer mode")}
required
.options=${issuerModeOptions}
.value=${provider?.issuerMode}
help=${msg(
"Configure how the issuer field of the ID Token should be filled.",
)}
>
</ak-radio-input>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Trusted OIDC Sources")}
name="jwksSources"
.errorMessages=${errors?.jwksSources ?? []}
>
<select class="pf-c-form-control" multiple>
${this.oauthSources?.results.map((source) => {
const selected = (provider?.jwksSources || []).some((su) => {
return su == source.pk;
}); });
} return html`<option value=${source.pk} ?selected=${selected}>
return html`<option ${source.name} (${source.slug})
value=${ifDefined(scope.pk)} </option>`;
?selected=${selected} })}
> </select>
${scope.name} <p class="pf-c-form__helper-text">
</option>`; ${msg(
})} "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
</select> )}
<p class="pf-c-form__helper-text"> </p>
${msg( <p class="pf-c-form__helper-text">
"Select which scopes can be used by the client. The client still has to specify the scope to access the data.", ${msg("Hold control/command to select multiple items.")}
)} </p>
</p> </ak-form-element-horizontal>
<p class="pf-c-form__helper-text"> </div>
${msg("Hold control/command to select multiple items.")} </ak-form-group>
</p> </form>`;
</ak-form-element-horizontal>
<ak-radio-input
name="subMode"
label=${msg("Subject mode")}
required
.options=${subjectModeOptions}
.value=${provider?.subMode}
help=${msg(
"Configure what data should be used as unique User Identifier. For most cases, the default should be fine.",
)}
>
</ak-radio-input>
<ak-switch-input
name="includeClaimsInIdToken"
label=${msg("Include claims in id_token")}
?checked=${first(provider?.includeClaimsInIdToken, true)}
help=${msg(
"Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
)}
></ak-switch-input>
<ak-radio-input
name="issuerMode"
label=${msg("Issuer mode")}
required
.options=${issuerModeOptions}
.value=${provider?.issuerMode}
help=${msg(
"Configure how the issuer field of the ID Token should be filled.",
)}
>
</ak-radio-input>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Trusted OIDC Sources")}
name="jwksSources"
>
<select class="pf-c-form-control" multiple>
${this.oauthSources?.results.map((source) => {
const selected = (provider?.jwksSources || []).some((su) => {
return su == source.pk;
});
return html`<option value=${source.pk} ?selected=${selected}>
${source.name} (${source.slug})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg(
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
} }
} }

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
@ -61,11 +62,11 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
return nothing; return nothing;
} }
renderProxyMode() { renderProxyMode(): TemplateResult {
return html`<h2>This space intentionally left blank</h2>`; throw new Error("Must be implemented in a child class.");
} }
renderHttpBasic(): TemplateResult { renderHttpBasic() {
return html`<ak-text-input return html`<ak-text-input
name="basicAuthUserAttribute" name="basicAuthUserAttribute"
label=${msg("HTTP-Basic Username Key")} label=${msg("HTTP-Basic Username Key")}
@ -87,168 +88,194 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
</ak-text-input>`; </ak-text-input>`;
} }
scopeMappingConfiguration(provider?: ProxyProvider) {
const propertyMappings = this.propertyMappings?.results ?? [];
const defaultScopes = () =>
propertyMappings
.filter((scope) => !(scope?.managed ?? "").startsWith("goauthentik.io/providers"))
.map((pm) => pm.pk);
const configuredScopes = (providerMappings: string[]) =>
propertyMappings.map((scope) => scope.pk).filter((pk) => providerMappings.includes(pk));
const scopeValues = provider?.propertyMappings
? configuredScopes(provider?.propertyMappings ?? [])
: defaultScopes();
const scopePairs = propertyMappings.map((scope) => [scope.pk, scope.name]);
return { scopePairs, scopeValues };
}
render() { render() {
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> const errors = this.wizard.errors.provider;
${this.renderModeDescription()} const { scopePairs, scopeValues } = this.scopeMappingConfiguration(this.instance);
<ak-text-input
name="name"
value=${ifDefined(this.instance?.name)}
required
label=${msg("Name")}
></ak-text-input>
<ak-form-element-horizontal return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title>
label=${msg("Authentication flow")} <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
?required=${false} ${this.renderModeDescription()}
name="authenticationFlow" <ak-text-input
> name="name"
<ak-flow-search value=${ifDefined(this.instance?.name)}
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authenticationFlow}
required required
></ak-flow-search> .errorMessages=${errors?.name ?? []}
<p class="pf-c-form__helper-text"> label=${msg("Name")}
${msg("Flow used when a user access this provider and is not authenticated.")} ></ak-text-input>
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Authorization flow")} label=${msg("Authentication flow")}
?required=${true} ?required=${false}
name="authorizationFlow" .errorMessages=${errors?.authenticationFlow ?? []}
> name="authenticationFlow"
<ak-flow-search >
flowType=${FlowsInstancesListDesignationEnum.Authorization} <ak-flow-search
.currentFlow=${this.instance?.authorizationFlow} flowType=${FlowsInstancesListDesignationEnum.Authentication}
required .currentFlow=${this.instance?.authenticationFlow}
></ak-flow-search> required
<p class="pf-c-form__helper-text"> ></ak-flow-search>
${msg("Flow used when authorizing this provider.")} <p class="pf-c-form__helper-text">
</p> ${msg(
</ak-form-element-horizontal> "Flow used when a user access this provider and is not authenticated.",
)}
</p>
</ak-form-element-horizontal>
${this.renderProxyMode()} <ak-form-element-horizontal
label=${msg("Authorization flow")}
?required=${true}
name="authorizationFlow"
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${this.instance?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-text-input ${this.renderProxyMode()}
name="accessTokenValidity"
value=${first(this.instance?.accessTokenValidity, "hours=24")}
label=${msg("Token validity")}
help=${msg("Configure how long tokens are valid for.")}
></ak-text-input>
<ak-form-group> <ak-text-input
<span slot="header">${msg("Advanced protocol settings")}</span> name="accessTokenValidity"
<div slot="body" class="pf-c-form"> value=${first(this.instance?.accessTokenValidity, "hours=24")}
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate"> label=${msg("Token validity")}
<ak-crypto-certificate-search help=${msg("Configure how long tokens are valid for.")}
certificate=${ifDefined(this.instance?.certificate ?? undefined)} .errorMessages=${errors?.accessTokenValidity ?? []}
></ak-crypto-certificate-search> ></ak-text-input>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-group>
label=${msg("Additional scopes")} <span slot="header">${msg("Advanced protocol settings")}</span>
name="propertyMappings" <div slot="body" class="pf-c-form">
> <ak-form-element-horizontal
<select class="pf-c-form-control" multiple> label=${msg("Certificate")}
${this.propertyMappings?.results name="certificate"
.filter((scope) => { .errorMessages=${errors?.certificate ?? []}
return !scope.managed?.startsWith("goauthentik.io/providers"); >
}) <ak-crypto-certificate-search
.map((scope) => { certificate=${ifDefined(this.instance?.certificate ?? undefined)}
const selected = (this.instance?.propertyMappings || []).some( ></ak-crypto-certificate-search>
</ak-form-element-horizontal>
<ak-multi-select
label=${msg("AdditionalScopes")}
name="propertyMappings"
required
.options=${scopePairs}
.values=${scopeValues}
.errorMessages=${errors?.propertyMappings ?? []}
.richhelp=${html`
<p class="pf-c-form__helper-text">
${msg(
"Additional scope mappings, which are passed to the proxy.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
`}
></ak-multi-select>
<ak-textarea-input
name="skipPathRegex"
label=${this.mode === ProxyMode.ForwardDomain
? msg("Unauthenticated URLs")
: msg("Unauthenticated Paths")}
value=${ifDefined(this.instance?.skipPathRegex)}
.errorMessages=${errors?.skipPathRegex ?? []}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg(
"Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
)}
</p>`}
>
</ak-textarea-input>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${msg("Authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="interceptHeaderAuth"
?checked=${first(this.instance?.interceptHeaderAuth, true)}
label=${msg("Intercept header authentication")}
help=${msg(
"When enabled, authentik will intercept the Authorization header to authenticate the request.",
)}
></ak-switch-input>
<ak-switch-input
name="basicAuthEnabled"
?checked=${first(this.instance?.basicAuthEnabled, false)}
@change=${(ev: Event) => {
const el = ev.target as HTMLInputElement;
this.showHttpBasic = el.checked;
}}
label=${msg("Send HTTP-Basic Authentication")}
help=${msg(
"Send a custom HTTP-Basic Authentication header based on values from authentik.",
)}
></ak-switch-input>
${this.showHttpBasic ? this.renderHttpBasic() : html``}
<ak-form-element-horizontal
label=${msg("Trusted OIDC Sources")}
name="jwksSources"
.errorMessages=${errors?.jwksSources ?? []}
>
<select class="pf-c-form-control" multiple>
${this.oauthSources?.results.map((source) => {
const selected = (this.instance?.jwksSources || []).some(
(su) => { (su) => {
return su == scope.pk; return su == source.pk;
}, },
); );
return html`<option return html`<option value=${source.pk} ?selected=${selected}>
value=${ifDefined(scope.pk)} ${source.name} (${source.slug})
?selected=${selected}
>
${scope.name}
</option>`; </option>`;
})} })}
</select> </select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Additional scope mappings, which are passed to the proxy.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-textarea-input
name="skipPathRegex"
label=${this.mode === ProxyMode.ForwardDomain
? msg("Unauthenticated URLs")
: msg("Unauthenticated Paths")}
value=${ifDefined(this.instance?.skipPathRegex)}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg( ${msg(
"Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.", "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
)} )}
</p> </p>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg( ${msg("Hold control/command to select multiple items.")}
"When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.", </p>
)} </ak-form-element-horizontal>
</p>`} </div>
> </ak-form-group>
</ak-textarea-input> </form>`;
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${msg("Authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="interceptHeaderAuth"
?checked=${first(this.instance?.interceptHeaderAuth, true)}
label=${msg("Intercept header authentication")}
help=${msg(
"When enabled, authentik will intercept the Authorization header to authenticate the request.",
)}
></ak-switch-input>
<ak-switch-input
name="basicAuthEnabled"
?checked=${first(this.instance?.basicAuthEnabled, false)}
@change=${(ev: Event) => {
const el = ev.target as HTMLInputElement;
this.showHttpBasic = el.checked;
}}
label=${msg("Send HTTP-Basic Authentication")}
help=${msg(
"Send a custom HTTP-Basic Authentication header based on values from authentik.",
)}
></ak-switch-input>
${this.showHttpBasic ? this.renderHttpBasic() : html``}
<ak-form-element-horizontal
label=${msg("Trusted OIDC Sources")}
name="jwksSources"
>
<select class="pf-c-form-control" multiple>
${this.oauthSources?.results.map((source) => {
const selected = (this.instance?.jwksSources || []).some((su) => {
return su == source.pk;
});
return html`<option value=${source.pk} ?selected=${selected}>
${source.name} (${source.slug})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg(
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
} }
} }

View File

@ -5,6 +5,8 @@ import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit"; import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { ProxyProvider } from "@goauthentik/api";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage"; import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-forward-proxy-domain") @customElement("ak-application-wizard-authentication-for-forward-proxy-domain")
@ -28,11 +30,15 @@ export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplic
} }
renderProxyMode() { renderProxyMode() {
const provider = this.wizard.provider as ProxyProvider | undefined;
const errors = this.wizard.errors.provider;
return html` return html`
<ak-text-input <ak-text-input
name="externalHost" name="externalHost"
label=${msg("External host")} label=${msg("External host")}
value=${ifDefined(this.instance?.externalHost)} value=${ifDefined(provider?.externalHost)}
.errorMessages=${errors?.externalHost ?? []}
required required
help=${msg( help=${msg(
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.", "The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
@ -42,7 +48,8 @@ export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplic
<ak-text-input <ak-text-input
name="cookieDomain" name="cookieDomain"
label=${msg("Cookie domain")} label=${msg("Cookie domain")}
value="${ifDefined(this.instance?.cookieDomain)}" value="${ifDefined(provider?.cookieDomain)}"
.errorMessages=${errors?.cookieDomain ?? []}
required required
help=${msg( help=${msg(
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.", "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",

View File

@ -7,6 +7,8 @@ import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit"; import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { ProxyProvider } from "@goauthentik/api";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage"; import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-reverse-proxy") @customElement("ak-application-wizard-authentication-for-reverse-proxy")
@ -20,25 +22,30 @@ export class AkReverseProxyApplicationWizardPage extends AkTypeProxyApplicationW
} }
renderProxyMode() { renderProxyMode() {
const provider = this.wizard.provider as ProxyProvider | undefined;
const errors = this.wizard.errors.provider;
return html` <ak-text-input return html` <ak-text-input
name="externalHost" name="externalHost"
value=${ifDefined(this.instance?.externalHost)} value=${ifDefined(provider?.externalHost)}
required required
label=${msg("External host")} label=${msg("External host")}
.errorMessages=${errors?.externalHost ?? []}
help=${msg( help=${msg(
"The external URL you'll access the application at. Include any non-standard port.", "The external URL you'll access the application at. Include any non-standard port.",
)} )}
></ak-text-input> ></ak-text-input>
<ak-text-input <ak-text-input
name="internalHost" name="internalHost"
value=${ifDefined(this.instance?.internalHost)} value=${ifDefined(provider?.internalHost)}
.errorMessages=${errors?.internalHost ?? []}
required required
label=${msg("Internal host")} label=${msg("Internal host")}
help=${msg("Upstream host that the requests are forwarded to.")} help=${msg("Upstream host that the requests are forwarded to.")}
></ak-text-input> ></ak-text-input>
<ak-switch-input <ak-switch-input
name="internalHostSslValidation" name="internalHostSslValidation"
?checked=${first(this.instance?.internalHostSslValidation, true)} ?checked=${first(provider?.internalHostSslValidation, true)}
label=${msg("Internal host SSL Validation")} label=${msg("Internal host SSL Validation")}
help=${msg("Validate SSL Certificates of upstream servers.")} help=${msg("Validate SSL Certificates of upstream servers.")}
> >

View File

@ -5,6 +5,8 @@ import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit"; import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { ProxyProvider } from "@goauthentik/api";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage"; import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-single-forward-proxy") @customElement("ak-application-wizard-authentication-for-single-forward-proxy")
@ -21,11 +23,15 @@ export class AkForwardSingleProxyApplicationWizardPage extends AkTypeProxyApplic
} }
renderProxyMode() { renderProxyMode() {
const provider = this.wizard.provider as ProxyProvider | undefined;
const errors = this.wizard.errors.provider;
return html`<ak-text-input return html`<ak-text-input
name="externalHost" name="externalHost"
value=${ifDefined(this.instance?.externalHost)} value=${ifDefined(provider?.externalHost)}
required required
label=${msg("External host")} label=${msg("External host")}
.errorMessages=${errors?.externalHost ?? []}
help=${msg( help=${msg(
"The external URL you'll access the application at. Include any non-standard port.", "The external URL you'll access the application at. Include any non-standard port.",
)} )}

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
@ -19,54 +20,62 @@ import BaseProviderPanel from "../BaseProviderPanel";
export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel { export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel {
render() { render() {
const provider = this.wizard.provider as RadiusProvider | undefined; const provider = this.wizard.provider as RadiusProvider | undefined;
const errors = this.wizard.errors.provider;
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> return html`<ak-wizard-title>${msg("Configure Radius Provider")}</ak-wizard-title>
<ak-text-input <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
name="name" <ak-text-input
label=${msg("Name")} name="name"
value=${ifDefined(provider?.name)} label=${msg("Name")}
required value=${ifDefined(provider?.name)}
> .errorMessages=${errors?.name ?? []}
</ak-text-input>
<ak-form-element-horizontal
label=${msg("Authentication flow")}
?required=${true}
name="authorizationFlow"
>
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.brandFlow=${rootInterface()?.brand?.flowAuthentication}
required required
></ak-branded-flow-search> >
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p> </ak-text-input>
</ak-form-element-horizontal>
<ak-form-group expanded> <ak-form-element-horizontal
<span slot="header"> ${msg("Protocol settings")} </span> label=${msg("Authentication flow")}
<div slot="body" class="pf-c-form"> ?required=${true}
<ak-text-input name="authorizationFlow"
name="sharedSecret" .errorMessages=${errors?.authorizationFlow ?? []}
label=${msg("Shared secret")} >
value=${first( <ak-branded-flow-search
provider?.sharedSecret, flowType=${FlowsInstancesListDesignationEnum.Authentication}
randomString(128, ascii_letters + digits), .currentFlow=${provider?.authorizationFlow}
)} .brandFlow=${rootInterface()?.brand?.flowAuthentication}
required required
></ak-text-input> ></ak-branded-flow-search>
<ak-text-input <p class="pf-c-form__helper-text">
name="clientNetworks" ${msg("Flow used for users to authenticate.")}
label=${msg("Client Networks")} </p>
value=${first(provider?.clientNetworks, "0.0.0.0/0, ::/0")} </ak-form-element-horizontal>
required
help=${msg(`List of CIDRs (comma-seperated) that clients can connect from. A more specific <ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="sharedSecret"
label=${msg("Shared secret")}
.errorMessages=${errors?.sharedSecret ?? []}
value=${first(
provider?.sharedSecret,
randomString(128, ascii_letters + digits),
)}
required
></ak-text-input>
<ak-text-input
name="clientNetworks"
label=${msg("Client Networks")}
value=${first(provider?.clientNetworks, "0.0.0.0/0, ::/0")}
.errorMessages=${errors?.clientNetworks ?? []}
required
help=${msg(`List of CIDRs (comma-seperated) that clients can connect from. A more specific
CIDR will match before a looser one. Clients connecting from a non-specified CIDR CIDR will match before a looser one. Clients connecting from a non-specified CIDR
will be dropped.`)} will be dropped.`)}
></ak-text-input> ></ak-text-input>
</div> </div>
</ak-form-group> </ak-form-group>
</form>`; </form>`;
} }
} }

View File

@ -1,7 +1,10 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search"; import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-multi-select";
import "@goauthentik/components/ak-number-input"; import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
@ -10,7 +13,7 @@ import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { customElement, state } from "@lit/reactive-element/decorators.js";
import { html } from "lit"; import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
@ -27,9 +30,11 @@ import {
signatureAlgorithmOptions, signatureAlgorithmOptions,
spBindingOptions, spBindingOptions,
} from "./SamlProviderOptions"; } from "./SamlProviderOptions";
import "./saml-property-mappings-search";
@customElement("ak-application-wizard-authentication-by-saml-configuration") @customElement("ak-application-wizard-authentication-by-saml-configuration")
export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPanel { export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPanel {
@state()
propertyMappings?: PaginatedSAMLPropertyMappingList; propertyMappings?: PaginatedSAMLPropertyMappingList;
constructor() { constructor() {
@ -43,207 +48,229 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
}); });
} }
propertyMappingConfiguration(provider?: SAMLProvider) {
const propertyMappings = this.propertyMappings?.results ?? [];
const configuredMappings = (providerMappings: string[]) =>
propertyMappings.map((pm) => pm.pk).filter((pmpk) => providerMappings.includes(pmpk));
const managedMappings = () =>
propertyMappings
.filter((pm) => (pm?.managed ?? "").startsWith("goauthentik.io/providers/saml"))
.map((pm) => pm.pk);
const pmValues = provider?.propertyMappings
? configuredMappings(provider?.propertyMappings ?? [])
: managedMappings();
const propertyPairs = propertyMappings.map((pm) => [pm.pk, pm.name]);
return { pmValues, propertyPairs };
}
render() { render() {
const provider = this.wizard.provider as SAMLProvider | undefined; const provider = this.wizard.provider as SAMLProvider | undefined;
const errors = this.wizard.errors.provider;
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> const { pmValues, propertyPairs } = this.propertyMappingConfiguration(provider);
<ak-text-input
name="name"
value=${ifDefined(provider?.name)}
required
label=${msg("Name")}
></ak-text-input>
<ak-form-element-horizontal return html` <ak-wizard-title>${msg("Configure SAML Provider")}</ak-wizard-title>
label=${msg("Authentication flow")} <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
?required=${false} <ak-text-input
name="authenticationFlow" name="name"
> value=${ifDefined(provider?.name)}
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authenticationFlow}
required required
></ak-flow-search> label=${msg("Name")}
<p class="pf-c-form__helper-text"> .errorMessages=${errors?.name ?? []}
${msg("Flow used when a user access this provider and is not authenticated.")} ></ak-text-input>
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Authorization flow")} label=${msg("Authentication flow")}
?required=${true} ?required=${false}
name="authorizationFlow" name="authenticationFlow"
> .errorMessages=${errors?.authenticationFlow ?? []}
<ak-flow-search >
flowType=${FlowsInstancesListDesignationEnum.Authorization} <ak-flow-search
.currentFlow=${provider?.authorizationFlow} flowType=${FlowsInstancesListDesignationEnum.Authentication}
required .currentFlow=${provider?.authenticationFlow}
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="acsUrl"
value=${ifDefined(provider?.acsUrl)}
required required
label=${msg("ACS URL")} ></ak-flow-search>
></ak-text-input> <p class="pf-c-form__helper-text">
${msg(
<ak-text-input "Flow used when a user access this provider and is not authenticated.",
name="issuer"
value=${provider?.issuer || "authentik"}
required
label=${msg("Issuer")}
help=${msg("Also known as EntityID.")}
></ak-text-input>
<ak-radio-input
name="spBinding"
label=${msg("Service Provider Binding")}
required
.options=${spBindingOptions}
.value=${provider?.spBinding}
help=${msg(
"Determines how authentik sends the response back to the Service Provider.",
)} )}
> </p>
</ak-radio-input> </ak-form-element-horizontal>
<ak-text-input <ak-form-element-horizontal
name="audience" label=${msg("Authorization flow")}
value=${ifDefined(provider?.audience)} ?required=${true}
label=${msg("Audience")} name="authorizationFlow"
></ak-text-input> .errorMessages=${errors?.authorizationFlow ?? []}
</div> >
</ak-form-group> <ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group> <ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Advanced protocol settings")} </span> <span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-form-element-horizontal <ak-text-input
label=${msg("Signing Certificate")} name="acsUrl"
name="signingKp" value=${ifDefined(provider?.acsUrl)}
> required
<ak-crypto-certificate-search label=${msg("ACS URL")}
certificate=${ifDefined(provider?.signingKp ?? undefined)} .errorMessages=${errors?.acsUrl ?? []}
></ak-crypto-certificate-search> ></ak-text-input>
<p class="pf-c-form__helper-text">
${msg( <ak-text-input
"Certificate used to sign outgoing Responses going to the Service Provider.", name="issuer"
value=${provider?.issuer || "authentik"}
required
label=${msg("Issuer")}
help=${msg("Also known as EntityID.")}
.errorMessages=${errors?.issuer ?? []}
></ak-text-input>
<ak-radio-input
name="spBinding"
label=${msg("Service Provider Binding")}
required
.options=${spBindingOptions}
.value=${provider?.spBinding}
help=${msg(
"Determines how authentik sends the response back to the Service Provider.",
)} )}
</p> >
</ak-form-element-horizontal> </ak-radio-input>
<ak-form-element-horizontal <ak-text-input
label=${msg("Verification Certificate")} name="audience"
name="verificationKp" value=${ifDefined(provider?.audience)}
> label=${msg("Audience")}
<ak-crypto-certificate-search .errorMessages=${errors?.audience ?? []}
certificate=${ifDefined(provider?.verificationKp ?? undefined)} ></ak-text-input>
nokey </div>
></ak-crypto-certificate-search> </ak-form-group>
<p class="pf-c-form__helper-text">
${msg(
"When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-group>
label=${msg("Property mappings")} <span slot="header"> ${msg("Advanced protocol settings")} </span>
?required=${true} <div slot="body" class="pf-c-form">
name="propertyMappings" <ak-form-element-horizontal
> label=${msg("Signing Certificate")}
<select class="pf-c-form-control" multiple> name="signingKp"
${this.propertyMappings?.results.map((mapping) => { >
let selected = false; <ak-crypto-certificate-search
if (!provider?.propertyMappings) { certificate=${ifDefined(provider?.signingKp ?? undefined)}
selected = ></ak-crypto-certificate-search>
mapping.managed?.startsWith( <p class="pf-c-form__helper-text">
"goauthentik.io/providers/saml", ${msg(
) || false; "Certificate used to sign outgoing Responses going to the Service Provider.",
} else { )}
selected = Array.from(provider?.propertyMappings).some((su) => { </p>
return su == mapping.pk; </ak-form-element-horizontal>
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("NameID Property Mapping")} label=${msg("Verification Certificate")}
name="nameIdMapping" name="verificationKp"
> >
<ak-saml-property-mapping-search <ak-crypto-certificate-search
certificate=${ifDefined(provider?.verificationKp ?? undefined)}
nokey
></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">
${msg(
"When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.",
)}
</p>
</ak-form-element-horizontal>
<ak-multi-select
label=${msg("Property Mappings")}
name="propertyMappings"
required
.options=${propertyPairs}
.values=${pmValues}
.richhelp=${html` <p class="pf-c-form__helper-text">
${msg("Property mappings used for user mapping.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>`}
></ak-multi-select>
<ak-form-element-horizontal
label=${msg("NameID Property Mapping")}
name="nameIdMapping" name="nameIdMapping"
propertymapping=${ifDefined(provider?.nameIdMapping ?? undefined)} >
></ak-saml-property-mapping-search> <ak-saml-property-mapping-search
<p class="pf-c-form__helper-text"> name="nameIdMapping"
${msg( propertymapping=${ifDefined(provider?.nameIdMapping ?? undefined)}
"Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.", ></ak-saml-property-mapping-search>
<p class="pf-c-form__helper-text">
${msg(
"Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.",
)}
</p>
</ak-form-element-horizontal>
<ak-text-input
name="assertionValidNotBefore"
value=${provider?.assertionValidNotBefore || "minutes=-5"}
required
label=${msg("Assertion valid not before")}
help=${msg(
"Configure the maximum allowed time drift for an assertion.",
)} )}
</p> .errorMessages=${errors?.assertionValidNotBefore ?? []}
</ak-form-element-horizontal> ></ak-text-input>
<ak-text-input <ak-text-input
name="assertionValidNotBefore" name="assertionValidNotOnOrAfter"
value=${provider?.assertionValidNotBefore || "minutes=-5"} value=${provider?.assertionValidNotOnOrAfter || "minutes=5"}
required required
label=${msg("Assertion valid not before")} label=${msg("Assertion valid not on or after")}
help=${msg("Configure the maximum allowed time drift for an assertion.")} help=${msg(
></ak-text-input> "Assertion not valid on or after current time + this value.",
)}
.errorMessages=${errors?.assertionValidNotOnOrAfter ?? []}
></ak-text-input>
<ak-text-input <ak-text-input
name="assertionValidNotOnOrAfter" name="sessionValidNotOnOrAfter"
value=${provider?.assertionValidNotOnOrAfter || "minutes=5"} value=${provider?.sessionValidNotOnOrAfter || "minutes=86400"}
required required
label=${msg("Assertion valid not on or after")} label=${msg("Session valid not on or after")}
help=${msg("Assertion not valid on or after current time + this value.")} help=${msg("Session not valid on or after current time + this value.")}
></ak-text-input> .errorMessages=${errors?.sessionValidNotOnOrAfter ?? []}
></ak-text-input>
<ak-text-input <ak-radio-input
name="sessionValidNotOnOrAfter" name="digestAlgorithm"
value=${provider?.sessionValidNotOnOrAfter || "minutes=86400"} label=${msg("Digest algorithm")}
required required
label=${msg("Session valid not on or after")} .options=${digestAlgorithmOptions}
help=${msg("Session not valid on or after current time + this value.")} .value=${provider?.digestAlgorithm}
></ak-text-input> >
</ak-radio-input>
<ak-radio-input <ak-radio-input
name="digestAlgorithm" name="signatureAlgorithm"
label=${msg("Digest algorithm")} label=${msg("Signature algorithm")}
required required
.options=${digestAlgorithmOptions} .options=${signatureAlgorithmOptions}
.value=${provider?.digestAlgorithm} .value=${provider?.signatureAlgorithm}
> >
</ak-radio-input> </ak-radio-input>
</div>
<ak-radio-input </ak-form-group>
name="signatureAlgorithm" </form>`;
label=${msg("Signature algorithm")}
required
.options=${signatureAlgorithmOptions}
.value=${provider?.signatureAlgorithm}
>
</ak-radio-input>
</div>
</ak-form-group>
</form>`;
} }
} }

View File

@ -1,81 +0,0 @@
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search-no-default";
import "@goauthentik/components/ak-file-input";
import { AkFileInput } from "@goauthentik/components/ak-file-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html } from "lit";
import { query } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
ProvidersSamlImportMetadataCreateRequest,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-saml-import")
export class ApplicationWizardProviderSamlImport extends BaseProviderPanel {
@query('ak-file-input[name="metadata"]')
fileInput!: AkFileInput;
handleChange(ev: InputEvent) {
if (!ev.target) {
console.warn(`Received event with no target: ${ev}`);
return;
}
const target = ev.target as HTMLInputElement;
if (target.type === "file") {
const file = this.fileInput.files?.[0] ?? null;
if (file) {
this.dispatchWizardUpdate({
update: {
provider: {
file,
},
},
status: this.form.checkValidity() ? "valid" : "invalid",
});
}
return;
}
super.handleChange(ev);
}
render() {
const provider = this.wizard.provider as
| ProvidersSamlImportMetadataCreateRequest
| undefined;
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
value=${ifDefined(provider?.name)}
label=${msg("Name")}
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
?required=${true}
name="authorizationFlow"
>
<ak-flow-search-no-default
flowType=${FlowsInstancesListDesignationEnum.Authorization}
required
></ak-flow-search-no-default>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-file-input name="metadata" label=${msg("Metadata")} required></ak-file-input>
</form>`;
}
}
export default ApplicationWizardProviderSamlImport;

View File

@ -1,7 +1,10 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-multi-select";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
@ -12,14 +15,7 @@ import { customElement, state } from "@lit/reactive-element/decorators.js";
import { html } from "lit"; import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { import { PaginatedSCIMMappingList, PropertymappingsApi, type SCIMProvider } from "@goauthentik/api";
CoreApi,
CoreGroupsListRequest,
type Group,
PaginatedSCIMMappingList,
PropertymappingsApi,
type SCIMProvider,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel"; import BaseProviderPanel from "../BaseProviderPanel";
@ -31,158 +27,129 @@ export class ApplicationWizardAuthenticationBySCIM extends BaseProviderPanel {
constructor() { constructor() {
super(); super();
new PropertymappingsApi(DEFAULT_CONFIG) new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({ .propertymappingsScimList({
ordering: "scope_name", ordering: "managed",
}) })
.then((propertyMappings: PaginatedSCIMMappingList) => { .then((propertyMappings: PaginatedSCIMMappingList) => {
this.propertyMappings = propertyMappings; this.propertyMappings = propertyMappings;
}); });
} }
propertyMappingConfiguration(provider?: SCIMProvider) {
const propertyMappings = this.propertyMappings?.results ?? [];
const configuredMappings = (providerMappings: string[]) =>
propertyMappings.map((pm) => pm.pk).filter((pmpk) => providerMappings.includes(pmpk));
const managedMappings = (key: string) =>
propertyMappings
.filter((pm) => pm.managed === `goauthentik.io/providers/scim/${key}`)
.map((pm) => pm.pk);
const pmUserValues = provider?.propertyMappings
? configuredMappings(provider?.propertyMappings ?? [])
: managedMappings("user");
const pmGroupValues = provider?.propertyMappingsGroup
? configuredMappings(provider?.propertyMappingsGroup ?? [])
: managedMappings("group");
const propertyPairs = propertyMappings.map((pm) => [pm.pk, pm.name]);
return { pmUserValues, pmGroupValues, propertyPairs };
}
render() { render() {
const provider = this.wizard.provider as SCIMProvider | undefined; const provider = this.wizard.provider as SCIMProvider | undefined;
const errors = this.wizard.errors.provider;
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> const { pmUserValues, pmGroupValues, propertyPairs } =
<ak-text-input this.propertyMappingConfiguration(provider);
name="name"
label=${msg("Name")} return html`<ak-wizard-title>${msg("Configure SCIM Provider")}</ak-wizard-title>
value=${ifDefined(provider?.name)} <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
required <ak-text-input
></ak-text-input> name="name"
<ak-form-group expanded> label=${msg("Name")}
<span slot="header"> ${msg("Protocol settings")} </span> value=${ifDefined(provider?.name)}
<div slot="body" class="pf-c-form"> .errorMessages=${errors?.name ?? []}
<ak-text-input required
name="url" ></ak-text-input>
label=${msg("URL")} <ak-form-group expanded>
value="${first(provider?.url, "")}" <span slot="header"> ${msg("Protocol settings")} </span>
required <div slot="body" class="pf-c-form">
help=${msg("SCIM base url, usually ends in /v2.")} <ak-text-input
> name="url"
</ak-text-input> label=${msg("URL")}
<ak-text-input value="${first(provider?.url, "")}"
name="token" required
label=${msg("Token")} help=${msg("SCIM base url, usually ends in /v2.")}
value="${first(provider?.token, "")}" .errorMessages=${errors?.url ?? []}
required
help=${msg(
"Token to authenticate with. Currently only bearer authentication is supported.",
)}
>
</ak-text-input>
</div>
</ak-form-group>
<ak-form-group expanded>
<span slot="header">${msg("User filtering")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="excludeUsersServiceAccount"
?checked=${first(provider?.excludeUsersServiceAccount, true)}
label=${msg("Exclude service accounts")}
></ak-switch-input>
<ak-form-element-horizontal label=${msg("Group")} name="filterGroup">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(
args,
);
return groups.results;
}}
.renderElement=${(group: Group): string => {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group ? group.pk : undefined;
}}
.selected=${(group: Group): boolean => {
return group.pk === provider?.filterGroup;
}}
?blankable=${true}
> >
</ak-search-select> </ak-text-input>
<p class="pf-c-form__helper-text"> <ak-text-input
${msg("Only sync users within the selected group.")} name="token"
</p> label=${msg("Token")}
</ak-form-element-horizontal> value="${first(provider?.token, "")}"
</div> .errorMessages=${errors?.token ?? []}
</ak-form-group> required
<ak-form-group ?expanded=${true}> help=${msg(
<span slot="header"> ${msg("Attribute mapping")} </span> "Token to authenticate with. Currently only bearer authentication is supported.",
<div slot="body" class="pf-c-form"> )}
<ak-form-element-horizontal >
label=${msg("User Property Mappings")} </ak-text-input>
?required=${true} </div>
name="propertyMappings" </ak-form-group>
> <ak-form-group expanded>
<select class="pf-c-form-control" multiple> <span slot="header">${msg("User filtering")}</span>
${this.propertyMappings?.results.map((mapping) => { <div slot="body" class="pf-c-form">
let selected = false; <ak-switch-input
if (!provider?.propertyMappings) { name="excludeUsersServiceAccount"
selected = ?checked=${first(provider?.excludeUsersServiceAccount, true)}
mapping.managed === "goauthentik.io/providers/scim/user" || label=${msg("Exclude service accounts")}
false; ></ak-switch-input>
} else { <ak-form-element-horizontal label=${msg("Group")} name="filterGroup">
selected = Array.from(provider?.propertyMappings).some((su) => { <ak-core-group-search
return su == mapping.pk; .group=${provider?.filterGroup}
}); ></ak-core-group-search>
} <p class="pf-c-form__helper-text">
return html`<option ${msg("Only sync users within the selected group.")}
value=${ifDefined(mapping.pk)} </p>
?selected=${selected} </ak-form-element-horizontal>
> </div>
${mapping.name} </ak-form-group>
</option>`; <ak-form-group ?expanded=${true}>
})} <span slot="header"> ${msg("Attribute mapping")} </span>
</select> <div slot="body" class="pf-c-form">
<p class="pf-c-form__helper-text"> <ak-multi-select
${msg("Property mappings used to user mapping.")} label=${msg("User Property Mappings")}
</p> required
<p class="pf-c-form__helper-text"> name="propertyMappings"
${msg("Hold control/command to select multiple items.")} .options=${propertyPairs}
</p> .values=${pmUserValues}
</ak-form-element-horizontal> .richhelp=${html` <p class="pf-c-form__helper-text">
<ak-form-element-horizontal ${msg("Property mappings used for user mapping.")}
label=${msg("Group Property Mappings")} </p>
?required=${true} <p class="pf-c-form__helper-text">
name="propertyMappingsGroup" ${msg("Hold control/command to select multiple items.")}
> </p>`}
<select class="pf-c-form-control" multiple> ></ak-multi-select>
${this.propertyMappings?.results.map((mapping) => { <ak-multi-select
let selected = false; label=${msg("Group Property Mappings")}
if (!provider?.propertyMappingsGroup) { required
selected = name="propertyMappingsGroup"
mapping.managed === "goauthentik.io/providers/scim/group"; .options=${propertyPairs}
} else { .values=${pmGroupValues}
selected = Array.from(provider?.propertyMappingsGroup).some( .richhelp=${html` <p class="pf-c-form__helper-text">
(su) => { ${msg("Property mappings used for group creation.")}
return su == mapping.pk; </p>
}, <p class="pf-c-form__helper-text">
); ${msg("Hold control/command to select multiple items.")}
} </p>`}
return html`<option ></ak-multi-select>
value=${ifDefined(mapping.pk)} </div>
?selected=${selected} </ak-form-group>
> </form>`;
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Property mappings used to group creation.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
} }
} }

View File

@ -15,6 +15,12 @@ import "./commit/ak-application-wizard-commit-application";
import "./methods/ak-application-wizard-authentication-method"; import "./methods/ak-application-wizard-authentication-method";
import { ApplicationStep as ApplicationStepType } from "./types"; import { ApplicationStep as ApplicationStepType } from "./types";
/**
* In the current implementation, all of the child forms have access to the wizard's
* global context, into which all data is written, and which is updated by events
* flowing into the top-level orchestrator.
*/
class ApplicationStep implements ApplicationStepType { class ApplicationStep implements ApplicationStepType {
id = "application"; id = "application";
label = "Application Details"; label = "Application Details";

View File

@ -3,7 +3,7 @@ import { customElement } from "@lit/reactive-element/decorators/custom-element.j
import { state } from "@lit/reactive-element/decorators/state.js"; import { state } from "@lit/reactive-element/decorators/state.js";
import { LitElement, html } from "lit"; import { LitElement, html } from "lit";
import applicationWizardContext from "../ContextIdentity"; import { applicationWizardContext } from "../ContextIdentity";
import type { ApplicationWizardState } from "../types"; import type { ApplicationWizardState } from "../types";
@customElement("ak-application-context-display-for-test") @customElement("ak-application-context-display-for-test")

View File

@ -9,6 +9,7 @@ import {
type RadiusProviderRequest, type RadiusProviderRequest,
type SAMLProviderRequest, type SAMLProviderRequest,
type SCIMProviderRequest, type SCIMProviderRequest,
type ValidationError,
} from "@goauthentik/api"; } from "@goauthentik/api";
export type OneOfProvider = export type OneOfProvider =
@ -24,12 +25,13 @@ export interface ApplicationWizardState {
providerModel: string; providerModel: string;
app: Partial<ApplicationRequest>; app: Partial<ApplicationRequest>;
provider: OneOfProvider; provider: OneOfProvider;
errors: ValidationError;
} }
type StatusType = "invalid" | "valid" | "submitted" | "failed"; type StatusType = "invalid" | "valid" | "submitted" | "failed";
export type ApplicationWizardStateUpdate = { export type ApplicationWizardStateUpdate = {
update?: Partial<ApplicationWizardState>; update?: ApplicationWizardState;
status?: StatusType; status?: StatusType;
}; };

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/events/EventVolumeChart";
import { EventGeo } from "@goauthentik/admin/events/utils"; import { EventGeo } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events"; import { EventWithContext } from "@goauthentik/common/events";
@ -10,7 +11,7 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { Event, EventsApi } from "@goauthentik/api"; import { Event, EventsApi } from "@goauthentik/api";
@ -35,6 +36,14 @@ export class EventListPage extends TablePage<Event> {
@property() @property()
order = "-created"; order = "-created";
static get styles(): CSSResult[] {
return super.styles.concat(css`
.pf-m-no-padding-bottom {
padding-bottom: 0;
}
`);
}
async apiEndpoint(page: number): Promise<PaginatedResponse<Event>> { async apiEndpoint(page: number): Promise<PaginatedResponse<Event>> {
return new EventsApi(DEFAULT_CONFIG).eventsEventsList({ return new EventsApi(DEFAULT_CONFIG).eventsEventsList({
ordering: this.order, ordering: this.order,
@ -55,6 +64,19 @@ export class EventListPage extends TablePage<Event> {
]; ];
} }
renderSectionBefore(): TemplateResult {
return html`
<div class="pf-c-page__main-section pf-m-no-padding-bottom">
<ak-events-volume-chart
.query=${{
page: this.page,
search: this.search,
}}
></ak-events-volume-chart>
</div>
`;
}
row(item: EventWithContext): TemplateResult[] { row(item: EventWithContext): TemplateResult[] {
return [ return [
html`<div>${actionToLabel(item.action)}</div> html`<div>${actionToLabel(item.action)}</div>

View File

@ -0,0 +1,63 @@
import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config";
import { AKChart } from "@goauthentik/app/elements/charts/Chart";
import { ChartData } from "chart.js";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import { Coordinate, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
@customElement("ak-events-volume-chart")
export class EventVolumeChart extends AKChart<Coordinate[]> {
_query?: EventsEventsListRequest;
@property({ attribute: false })
set query(value: EventsEventsListRequest | undefined) {
this._query = value;
this.refreshHandler();
}
static get styles(): CSSResult[] {
return super.styles.concat(
PFCard,
css`
.pf-c-card__body {
height: 12rem;
}
`,
);
}
apiRequest(): Promise<Coordinate[]> {
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList(this._query);
}
getChartData(data: Coordinate[]): ChartData {
return {
datasets: [
{
label: msg("Events"),
backgroundColor: "rgba(189, 229, 184, .5)",
spanGaps: true,
data:
data.map((cord) => {
return {
x: cord.xCord || 0,
y: cord.yCord || 0,
};
}) || [],
},
],
};
}
render(): TemplateResult {
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Event volume")}</div>
<div class="pf-c-card__body">${super.render()}</div>
</div>`;
}
}

View File

@ -1,120 +0,0 @@
/** Taken from: https://github.com/zellwk/javascript/tree/master
*
* We have added some typescript annotations, but this is such a rich feature with deep nesting
* we'll just have to watch it closely for any issues. So far there don't seem to be any.
*
*/
function objectType<T>(value: T) {
return Object.prototype.toString.call(value);
}
// Creates a deep clone for each value
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function cloneDescriptorValue(value: any) {
// Arrays
if (objectType(value) === "[object Array]") {
const array = [];
for (let v of value) {
v = cloneDescriptorValue(v);
array.push(v);
}
return array;
}
// Objects
if (objectType(value) === "[object Object]") {
const obj = {};
const props = Object.keys(value);
for (const prop of props) {
const descriptor = Object.getOwnPropertyDescriptor(value, prop);
if (!descriptor) {
continue;
}
if (descriptor.value) {
descriptor.value = cloneDescriptorValue(descriptor.value);
}
Object.defineProperty(obj, prop, descriptor);
}
return obj;
}
// Other Types of Objects
if (objectType(value) === "[object Date]") {
return new Date(value.getTime());
}
if (objectType(value) === "[object Map]") {
const map = new Map();
for (const entry of value) {
map.set(entry[0], cloneDescriptorValue(entry[1]));
}
return map;
}
if (objectType(value) === "[object Set]") {
const set = new Set();
for (const entry of value.entries()) {
set.add(cloneDescriptorValue(entry[0]));
}
return set;
}
// Types we don't need to clone or cannot clone.
// Examples:
// - Primitives don't need to clone
// - Functions cannot clone
return value;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function _merge(output: Record<string, any>, input: Record<string, any>) {
const props = Object.keys(input);
for (const prop of props) {
// Prevents Prototype Pollution
if (prop === "__proto__") continue;
const descriptor = Object.getOwnPropertyDescriptor(input, prop);
if (!descriptor) {
continue;
}
const value = descriptor.value;
if (value) descriptor.value = cloneDescriptorValue(value);
// If don't have prop => Define property
// [ken@goauthentik] Using `hasOwn` is preferable over
// the basic identity test, according to Typescript.
if (!Object.hasOwn(output, prop)) {
Object.defineProperty(output, prop, descriptor);
continue;
}
// If have prop, but type is not object => Overwrite by redefining property
if (typeof output[prop] !== "object") {
Object.defineProperty(output, prop, descriptor);
continue;
}
// If have prop, but type is Object => Concat the arrays together.
if (objectType(descriptor.value) === "[object Array]") {
output[prop] = output[prop].concat(descriptor.value);
continue;
}
// If have prop, but type is Object => Merge.
_merge(output[prop], descriptor.value);
}
}
export function merge(...sources: Array<object>) {
const result = {};
for (const source of sources) {
_merge(result, source);
}
return result;
}
export default merge;

View File

@ -54,6 +54,13 @@ export function camelToSnake(key: string): string {
return result.split(" ").join("_").toLowerCase(); return result.split(" ").join("_").toLowerCase();
} }
const capitalize = (key: string) => (key.length === 0 ? "" : key[0].toUpperCase() + key.slice(1));
export function snakeToCamel(key: string) {
const [start, ...rest] = key.split("_");
return [start, ...rest.map(capitalize)].join("");
}
export function groupBy<T>(objects: T[], callback: (obj: T) => string): Array<[string, T[]]> { export function groupBy<T>(objects: T[], callback: (obj: T) => string): Array<[string, T[]]> {
const m = new Map<string, T[]>(); const m = new Map<string, T[]>();
objects.forEach((obj) => { objects.forEach((obj) => {

View File

@ -0,0 +1,72 @@
import { AKElement } from "@goauthentik/elements/Base";
import { TemplateResult, html, nothing } from "lit";
import { property } from "lit/decorators.js";
type HelpType = TemplateResult | typeof nothing;
export class HorizontalLightComponent extends AKElement {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: Boolean })
required = false;
@property({ type: String })
help = "";
@property({ type: Object })
bighelp?: TemplateResult | TemplateResult[];
@property({ type: Boolean })
hidden = false;
@property({ type: Boolean })
invalid = false;
@property({ attribute: false })
errorMessages: string[] = [];
renderControl() {
throw new Error("Must be implemented in a subclass");
}
renderHelp(): HelpType[] {
const bigHelp: HelpType[] = Array.isArray(this.bighelp)
? this.bighelp
: [this.bighelp ?? nothing];
return [
this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
...bigHelp,
];
}
render() {
// prettier-ignore
return html`<ak-form-element-horizontal
label=${this.label}
?required=${this.required}
?hidden=${this.hidden}
name=${this.name}
.errorMessages=${this.errorMessages}
?invalid=${this.invalid}
>
${this.renderControl()}
${this.renderHelp()}
</ak-form-element-horizontal> `;
}
}

View File

@ -0,0 +1,150 @@
import "@goauthentik/app/elements/forms/HorizontalFormElement";
import { AKElement } from "@goauthentik/elements/Base";
import { TemplateResult, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { map } from "lit/directives/map.js";
import { Ref, createRef, ref } from "lit/directives/ref.js";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
type Pair = [string, string];
const selectStyles = css`
select[multiple] {
min-height: 15rem;
}
`;
/**
* Horizontal layout control with a multi-select.
*
* @part select - The select itself, to override the height specified above.
*/
@customElement("ak-multi-select")
export class AkMultiSelect extends AKElement {
constructor() {
super();
this.dataset.akControl = "true";
}
static get styles() {
return [PFBase, PFForm, PFFormControl, selectStyles];
}
/**
* The [name] attribute, which is also distributed to the layout manager and the input control.
*/
@property({ type: String })
name!: string;
/**
* The text label to display on the control
*/
@property({ type: String })
label = "";
/**
* The values to be displayed in the select. The format is [Value, Label], where the label is
* what will be displayed.
*/
@property({ attribute: false })
options: Pair[] = [];
/**
* If true, at least one object must be selected
*/
@property({ type: Boolean })
required = false;
/**
* Supporting a simple help string
*/
@property({ type: String })
help = "";
/**
* For more complex help instructions, provide a template result.
*/
@property({ type: Object })
bighelp!: TemplateResult | TemplateResult[];
/**
* An array of strings representing the objects currently selected.
*/
@property({ type: Array })
values: string[] = [];
/**
* Helper accessor for older code
*/
get value() {
return this.values;
}
/**
* One of two criteria (the other being the data-ak-control flag) that specifies this as a
* control that produces values of specific interest to our REST API. This is our modern
* accessor name.
*/
json() {
return this.values;
}
renderHelp() {
return [
this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
this.bighelp ? this.bighelp : nothing,
];
}
handleChange(ev: Event) {
if (ev.type === "change") {
this.values = Array.from(this.selectRef.value!.querySelectorAll("option"))
.filter((option) => option.selected)
.map((option) => option.value);
this.dispatchEvent(
new CustomEvent("ak-select", {
detail: this.values,
composed: true,
bubbles: true,
}),
);
}
}
selectRef: Ref<HTMLSelectElement> = createRef();
render() {
return html` <div class="pf-c-form">
<ak-form-element-horizontal
label=${this.label}
?required=${this.required}
name=${this.name}
>
<select
part="select"
class="pf-c-form-control"
name=${ifDefined(this.name)}
multiple
${ref(this.selectRef)}
@change=${this.handleChange}
>
${map(
this.options,
([value, label]) =>
html`<option value=${value} ?selected=${this.values.includes(value)}>
${label}
</option>`,
)}
</select>
${this.renderHelp()}
</ak-form-element-horizontal>
</div>`;
}
}
export default AkMultiSelect;

View File

@ -1,51 +1,21 @@
import { AKElement } from "@goauthentik/elements/Base"; import { html } from "lit";
import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { HorizontalLightComponent } from "./HorizontalLightComponent";
@customElement("ak-number-input") @customElement("ak-number-input")
export class AkNumberInput extends AKElement { export class AkNumberInput extends HorizontalLightComponent {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: Number, reflect: true }) @property({ type: Number, reflect: true })
value = 0; value = 0;
@property({ type: Boolean }) renderControl() {
required = false; return html`<input
type="number"
@property({ type: String }) value=${ifDefined(this.value)}
help = ""; class="pf-c-form-control"
render() {
return html`<ak-form-element-horizontal
label=${this.label}
?required=${this.required} ?required=${this.required}
name=${this.name} />`;
>
<input
type="number"
value=${ifDefined(this.value)}
class="pf-c-form-control"
?required=${this.required}
/>
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
</ak-form-element-horizontal> `;
} }
} }

View File

@ -1,35 +1,13 @@
import { AKElement } from "@goauthentik/elements/Base";
import { RadioOption } from "@goauthentik/elements/forms/Radio"; import { RadioOption } from "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { HorizontalLightComponent } from "./HorizontalLightComponent";
@customElement("ak-radio-input") @customElement("ak-radio-input")
export class AkRadioInput<T> extends AKElement { export class AkRadioInput<T> extends HorizontalLightComponent {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: String })
help = "";
@property({ type: Boolean })
required = false;
@property({ type: Object }) @property({ type: Object })
value!: T; value!: T;
@ -37,24 +15,25 @@ export class AkRadioInput<T> extends AKElement {
options: RadioOption<T>[] = []; options: RadioOption<T>[] = [];
handleInput(ev: CustomEvent) { handleInput(ev: CustomEvent) {
this.value = ev.detail.value; if ("detail" in ev) {
this.value = ev.detail.value;
}
} }
render() { renderHelp() {
return html`<ak-form-element-horizontal // This is weird, but Typescript says it's necessary?
label=${this.label} return [nothing as typeof nothing];
?required=${this.required} }
name=${this.name}
> renderControl() {
<ak-radio return html`<ak-radio
.options=${this.options} .options=${this.options}
.value=${this.value} .value=${this.value}
@input=${this.handleInput} @input=${this.handleInput}
></ak-radio> ></ak-radio>
${this.help.trim() ${this.help.trim()
? html`<p class="pf-c-form__helper-radio">${this.help}</p>` ? html`<p class="pf-c-form__helper-radio">${this.help}</p>`
: nothing} : nothing}`;
</ak-form-element-horizontal> `;
} }
} }

View File

@ -1,44 +1,16 @@
import { convertToSlug } from "@goauthentik/common/utils"; import { convertToSlug } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
import { TemplateResult, html, nothing } from "lit"; import { html } from "lit";
import { customElement, property, query } from "lit/decorators.js"; import { customElement, property, query } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { HorizontalLightComponent } from "./HorizontalLightComponent";
@customElement("ak-slug-input") @customElement("ak-slug-input")
export class AkSlugInput extends AKElement { export class AkSlugInput extends HorizontalLightComponent {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: String, reflect: true }) @property({ type: String, reflect: true })
value = ""; value = "";
@property({ type: Boolean })
required = false;
@property({ type: String })
help = "";
@property({ type: Boolean })
hidden = false;
@property({ type: Object })
bighelp!: TemplateResult | TemplateResult[];
@property({ type: String }) @property({ type: String })
source = ""; source = "";
@ -59,13 +31,6 @@ export class AkSlugInput extends AKElement {
this.input.addEventListener("input", this.handleTouch); this.input.addEventListener("input", this.handleTouch);
} }
renderHelp() {
return [
this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
this.bighelp ? this.bighelp : nothing,
];
}
// Do not stop propagation of this event; it must be sent up the tree so that a parent // Do not stop propagation of this event; it must be sent up the tree so that a parent
// component, such as a custom forms manager, may receive it. // component, such as a custom forms manager, may receive it.
handleTouch(ev: Event) { handleTouch(ev: Event) {
@ -150,21 +115,13 @@ export class AkSlugInput extends AKElement {
super.disconnectedCallback(); super.disconnectedCallback();
} }
render() { renderControl() {
return html`<ak-form-element-horizontal return html`<input
label=${this.label} type="text"
value=${ifDefined(this.value)}
class="pf-c-form-control"
?required=${this.required} ?required=${this.required}
?hidden=${this.hidden} />`;
name=${this.name}
>
<input
type="text"
value=${ifDefined(this.value)}
class="pf-c-form-control"
?required=${this.required}
/>
${this.renderHelp()}
</ak-form-element-horizontal> `;
} }
} }

View File

@ -1,65 +1,21 @@
import { AKElement } from "@goauthentik/elements/Base"; import { html } from "lit";
import { TemplateResult, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { HorizontalLightComponent } from "./HorizontalLightComponent";
@customElement("ak-text-input") @customElement("ak-text-input")
export class AkTextInput extends AKElement { export class AkTextInput extends HorizontalLightComponent {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: String, reflect: true }) @property({ type: String, reflect: true })
value = ""; value = "";
@property({ type: Boolean }) renderControl() {
required = false; return html` <input
type="text"
@property({ type: String }) value=${ifDefined(this.value)}
help = ""; class="pf-c-form-control"
@property({ type: Boolean })
hidden = false;
@property({ type: Object })
bighelp!: TemplateResult | TemplateResult[];
renderHelp() {
return [
this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
this.bighelp ? this.bighelp : nothing,
];
}
render() {
return html`<ak-form-element-horizontal
label=${this.label}
?required=${this.required} ?required=${this.required}
?hidden=${this.hidden} />`;
name=${this.name}
>
<input
type="text"
value=${ifDefined(this.value)}
class="pf-c-form-control"
?required=${this.required}
/>
${this.renderHelp()}
</ak-form-element-horizontal> `;
} }
} }

View File

@ -1,57 +1,22 @@
import { AKElement } from "@goauthentik/elements/Base"; import { html } from "lit";
import { TemplateResult, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { HorizontalLightComponent } from "./HorizontalLightComponent";
@customElement("ak-textarea-input") @customElement("ak-textarea-input")
export class AkTextareaInput extends AKElement { export class AkTextareaInput extends HorizontalLightComponent {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but @property({ type: String, reflect: true })
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: String })
value = ""; value = "";
@property({ type: Boolean }) renderControl() {
required = false; // Prevent the leading spaces added by Prettier's whitespace algo
// prettier-ignore
@property({ type: String }) return html`<textarea
help = ""; class="pf-c-form-control"
@property({ type: Object })
bighelp!: TemplateResult | TemplateResult[];
renderHelp() {
return [
this.help ? html`<p class="pf-c-form__helper-textarea">${this.help}</p>` : nothing,
this.bighelp ? this.bighelp : nothing,
];
}
render() {
return html`<ak-form-element-horizontal
label=${this.label}
?required=${this.required} ?required=${this.required}
name=${this.name} name=${this.name}
> >${this.value !== undefined ? this.value : ""}</textarea
<textarea class="pf-c-form-control" ?required=${this.required} name=${this.name}> > `;
${this.value !== undefined ? this.value : ""}</textarea
>
${this.renderHelp()}
</ak-form-element-horizontal> `;
} }
} }

View File

@ -0,0 +1,79 @@
import "@goauthentik/elements/messages/MessageContainer";
import { Meta } from "@storybook/web-components";
import { TemplateResult, html, render } from "lit";
import "../ak-multi-select";
import AkMultiSelect from "../ak-multi-select";
const metadata: Meta<AkMultiSelect> = {
title: "Components / MultiSelect",
component: "ak-multi-select",
parameters: {
docs: {
description: {
component: "A stylized value control for multi-select displays",
},
},
},
};
export default metadata;
const container = (testItem: TemplateResult) =>
html` <div style="background: #fff; padding: 2em">
<style>
li {
display: block;
}
p {
margin-top: 1em;
}
</style>
${testItem}
<div id="message-pad" style="margin-top: 1em"></div>
</div>`;
const testOptions = [
["funky", "Option One: Funky"],
["strange", "Option Two: Strange"],
["weird", "Option Three: Weird"],
];
export const RadioInput = () => {
const result = "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const displayChange = (ev: any) => {
const messagePad = document.getElementById("message-pad");
const component: AkMultiSelect | null = document.querySelector(
'ak-multi-select[name="ak-test-multi-select"]',
);
const results = html`
<p>Results from event:</p>
<ul style="list-style-type: disc">
${ev.target.value.map((v: string) => html`<li>${v}</li>`)}
</ul>
<p>Results from component:</p>
<ul style="list-style-type: disc">
${component!.json().map((v: string) => html`<li>${v}</li>`)}
</ul>
`;
render(results, messagePad!);
};
return container(
html`<ak-multi-select
@ak-select=${displayChange}
label="Test Radio Button"
name="ak-test-multi-select"
help="This is where you would read the help messages"
.options=${testOptions}
></ak-multi-select>
<div>${result}</div>`,
);
};

View File

@ -31,10 +31,15 @@ export interface KeyUnknown {
[key: string]: unknown; [key: string]: unknown;
} }
// Literally the only field `assignValue()` cares about.
type HTMLNamedElement = Pick<HTMLInputElement, "name">;
type AkControlElement = HTMLInputElement & { json: () => string | string[] };
/** /**
* Recursively assign `value` into `json` while interpreting the dot-path of `element.name` * Recursively assign `value` into `json` while interpreting the dot-path of `element.name`
*/ */
function assignValue(element: HTMLInputElement, value: unknown, json: KeyUnknown): void { function assignValue(element: HTMLNamedElement, value: unknown, json: KeyUnknown): void {
let parent = json; let parent = json;
if (!element.name?.includes(".")) { if (!element.name?.includes(".")) {
parent[element.name] = value; parent[element.name] = value;
@ -60,6 +65,16 @@ export function serializeForm<T extends KeyUnknown>(
const json: { [key: string]: unknown } = {}; const json: { [key: string]: unknown } = {};
elements.forEach((element) => { elements.forEach((element) => {
element.requestUpdate(); element.requestUpdate();
if (element.hidden) {
return;
}
// TODO: Tighten up the typing so that we can handle both.
if ("akControl" in element.dataset) {
assignValue(element, (element as unknown as AkControlElement).json(), json);
return;
}
const inputElement = element.querySelector<HTMLInputElement>("[name]"); const inputElement = element.querySelector<HTMLInputElement>("[name]");
if (element.hidden || !inputElement) { if (element.hidden || !inputElement) {
return; return;

View File

@ -5800,12 +5800,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s848288f8c2265aad"> <trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source> <source>Your application has been saved</source>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
</trans-unit> </trans-unit>
@ -6075,6 +6069,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6077,12 +6077,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s848288f8c2265aad"> <trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source> <source>Your application has been saved</source>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
</trans-unit> </trans-unit>
@ -6352,6 +6346,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5716,12 +5716,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s848288f8c2265aad"> <trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source> <source>Your application has been saved</source>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
</trans-unit> </trans-unit>
@ -5991,6 +5985,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -7617,14 +7617,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<source>Your application has been saved</source> <source>Your application has been saved</source>
<target>L'application a été sauvegardée</target> <target>L'application a été sauvegardée</target>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
<target>Dans l'application :</target>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
<target>Dans le fournisseur :</target>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
<target>Nom d'affichage de la méthode.</target> <target>Nom d'affichage de la méthode.</target>
@ -7986,6 +7978,54 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
<target>Lorsqu'activé, l'étape acceptera toujours l'identifiant utilisateur donné et continuera.</target> <target>Lorsqu'activé, l'étape acceptera toujours l'identifiant utilisateur donné et continuera.</target>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5924,12 +5924,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s848288f8c2265aad"> <trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source> <source>Your application has been saved</source>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
</trans-unit> </trans-unit>
@ -6199,6 +6193,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -7556,14 +7556,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Your application has been saved</source> <source>Your application has been saved</source>
<target>Ŷōũŕ àƥƥĺĩćàţĩōń ĥàś ƀēēń śàvēď</target> <target>Ŷōũŕ àƥƥĺĩćàţĩōń ĥàś ƀēēń śàvēď</target>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
<target>Ĩń ţĥē Àƥƥĺĩćàţĩōń:</target>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
<target>Ĩń ţĥē Ƥŕōvĩďēŕ:</target>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
<target>Mēţĥōď'ś ďĩśƥĺàŷ Ńàmē.</target> <target>Mēţĥōď'ś ďĩśƥĺàŷ Ńàmē.</target>
@ -7732,149 +7724,261 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s4bd386db7302bb22"> <trans-unit id="s4bd386db7302bb22">
<source>Create With Wizard</source> <source>Create With Wizard</source>
<target>Ćŕēàţē Ŵĩţĥ Ŵĩźàŕď</target>
</trans-unit> </trans-unit>
<trans-unit id="s070fdfb03034ca9b"> <trans-unit id="s070fdfb03034ca9b">
<source>One hint, 'New Application Wizard', is currently hidden</source> <source>One hint, 'New Application Wizard', is currently hidden</source>
<target>Ōńē ĥĩńţ, 'Ńēŵ Àƥƥĺĩćàţĩōń Ŵĩźàŕď', ĩś ćũŕŕēńţĺŷ ĥĩďďēń</target>
</trans-unit> </trans-unit>
<trans-unit id="s61bd841e66966325"> <trans-unit id="s61bd841e66966325">
<source>External applications that use authentik as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source> <source>External applications that use authentik as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
<target>Ēxţēŕńàĺ àƥƥĺĩćàţĩōńś ţĥàţ ũśē àũţĥēńţĩķ àś àń ĩďēńţĩţŷ ƥŕōvĩďēŕ vĩà ƥŕōţōćōĺś ĺĩķē ŌÀũţĥ2 àńď ŚÀMĹ. Àĺĺ àƥƥĺĩćàţĩōńś àŕē śĥōŵń ĥēŕē, ēvēń ōńēś ŷōũ ćàńńōţ àććēśś.</target>
</trans-unit> </trans-unit>
<trans-unit id="s1cc306d8e28c4464"> <trans-unit id="s1cc306d8e28c4464">
<source>Deny message</source> <source>Deny message</source>
<target>Ďēńŷ mēśśàĝē</target>
</trans-unit> </trans-unit>
<trans-unit id="s6985c401e1100122"> <trans-unit id="s6985c401e1100122">
<source>Message shown when this stage is run.</source> <source>Message shown when this stage is run.</source>
<target>Mēśśàĝē śĥōŵń ŵĥēń ţĥĩś śţàĝē ĩś ŕũń.</target>
</trans-unit> </trans-unit>
<trans-unit id="s09f0c100d0ad2fec"> <trans-unit id="s09f0c100d0ad2fec">
<source>Open Wizard</source> <source>Open Wizard</source>
<target>Ōƥēń Ŵĩźàŕď</target>
</trans-unit> </trans-unit>
<trans-unit id="sf2ef885f7d0a101d"> <trans-unit id="sf2ef885f7d0a101d">
<source>Demo Wizard</source> <source>Demo Wizard</source>
<target>Ďēmō Ŵĩźàŕď</target>
</trans-unit> </trans-unit>
<trans-unit id="s77505ee5d2e45e53"> <trans-unit id="s77505ee5d2e45e53">
<source>Run the demo wizard</source> <source>Run the demo wizard</source>
<target>Ŕũń ţĥē ďēmō ŵĩźàŕď</target>
</trans-unit> </trans-unit>
<trans-unit id="s4498e890d47a8066"> <trans-unit id="s4498e890d47a8066">
<source>OAuth2/OIDC (Open Authorization/OpenID Connect)</source> <source>OAuth2/OIDC (Open Authorization/OpenID Connect)</source>
<target>ŌÀũţĥ2/ŌĨĎĆ (Ōƥēń Àũţĥōŕĩźàţĩōń/ŌƥēńĨĎ Ćōńńēćţ)</target>
</trans-unit> </trans-unit>
<trans-unit id="s4f2e195d09e2868c"> <trans-unit id="s4f2e195d09e2868c">
<source>LDAP (Lightweight Directory Access Protocol)</source> <source>LDAP (Lightweight Directory Access Protocol)</source>
<target>ĹĎÀƤ (Ĺĩĝĥţŵēĩĝĥţ Ďĩŕēćţōŕŷ Àććēśś Ƥŕōţōćōĺ)</target>
</trans-unit> </trans-unit>
<trans-unit id="s7f5bb0c9923315ed"> <trans-unit id="s7f5bb0c9923315ed">
<source>Forward Auth (Single Application)</source> <source>Forward Auth (Single Application)</source>
<target>Ƒōŕŵàŕď Àũţĥ (Śĩńĝĺē Àƥƥĺĩćàţĩōń)</target>
</trans-unit> </trans-unit>
<trans-unit id="sf8008d2d6b064b95"> <trans-unit id="sf8008d2d6b064b95">
<source>Forward Auth (Domain Level)</source> <source>Forward Auth (Domain Level)</source>
<target>Ƒōŕŵàŕď Àũţĥ (Ďōmàĩń Ĺēvēĺ)</target>
</trans-unit> </trans-unit>
<trans-unit id="sfa8a1ffa9fee07d3"> <trans-unit id="sfa8a1ffa9fee07d3">
<source>SAML (Security Assertion Markup Language)</source> <source>SAML (Security Assertion Markup Language)</source>
<target>ŚÀMĹ (Śēćũŕĩţŷ Àśśēŕţĩōń Màŕķũƥ Ĺàńĝũàĝē)</target>
</trans-unit> </trans-unit>
<trans-unit id="s848a23972e388662"> <trans-unit id="s848a23972e388662">
<source>RADIUS (Remote Authentication Dial-In User Service)</source> <source>RADIUS (Remote Authentication Dial-In User Service)</source>
<target>ŔÀĎĨŨŚ (Ŕēmōţē Àũţĥēńţĩćàţĩōń Ďĩàĺ-Ĩń Ũśēŕ Śēŕvĩćē)</target>
</trans-unit> </trans-unit>
<trans-unit id="s3e902999ddf7b50e"> <trans-unit id="s3e902999ddf7b50e">
<source>SCIM (System for Cross-domain Identity Management)</source> <source>SCIM (System for Cross-domain Identity Management)</source>
<target>ŚĆĨM (Śŷśţēm ƒōŕ Ćŕōśś-ďōmàĩń Ĩďēńţĩţŷ Màńàĝēmēńţ)</target>
</trans-unit> </trans-unit>
<trans-unit id="sdc5690be4a342985"> <trans-unit id="sdc5690be4a342985">
<source>The token has been copied to your clipboard</source> <source>The token has been copied to your clipboard</source>
<target>Ţĥē ţōķēń ĥàś ƀēēń ćōƥĩēď ţō ŷōũŕ ćĺĩƥƀōàŕď</target>
</trans-unit> </trans-unit>
<trans-unit id="s7f3edfee24690c9f"> <trans-unit id="s7f3edfee24690c9f">
<source>The token was displayed because authentik does not have permission to write to the clipboard</source> <source>The token was displayed because authentik does not have permission to write to the clipboard</source>
<target>Ţĥē ţōķēń ŵàś ďĩśƥĺàŷēď ƀēćàũśē àũţĥēńţĩķ ďōēś ńōţ ĥàvē ƥēŕmĩśśĩōń ţō ŵŕĩţē ţō ţĥē ćĺĩƥƀōàŕď</target>
</trans-unit> </trans-unit>
<trans-unit id="saf6097bfa25205b8"> <trans-unit id="saf6097bfa25205b8">
<source>A copy of this recovery link has been placed in your clipboard</source> <source>A copy of this recovery link has been placed in your clipboard</source>
<target>À ćōƥŷ ōƒ ţĥĩś ŕēćōvēŕŷ ĺĩńķ ĥàś ƀēēń ƥĺàćēď ĩń ŷōũŕ ćĺĩƥƀōàŕď</target>
</trans-unit> </trans-unit>
<trans-unit id="s5b8ee296ed258568"> <trans-unit id="s5b8ee296ed258568">
<source>The current tenant must have a recovery flow configured to use a recovery link</source> <source>The current tenant must have a recovery flow configured to use a recovery link</source>
<target>Ţĥē ćũŕŕēńţ ţēńàńţ mũśţ ĥàvē à ŕēćōvēŕŷ ƒĺōŵ ćōńƒĩĝũŕēď ţō ũśē à ŕēćōvēŕŷ ĺĩńķ</target>
</trans-unit> </trans-unit>
<trans-unit id="s895514dda9cb9c94"> <trans-unit id="s895514dda9cb9c94">
<source>Create recovery link</source> <source>Create recovery link</source>
<target>Ćŕēàţē ŕēćōvēŕŷ ĺĩńķ</target>
</trans-unit> </trans-unit>
<trans-unit id="se5c795faf2c07514"> <trans-unit id="se5c795faf2c07514">
<source>Create Recovery Link</source> <source>Create Recovery Link</source>
<target>Ćŕēàţē Ŕēćōvēŕŷ Ĺĩńķ</target>
</trans-unit> </trans-unit>
<trans-unit id="s84fcddede27b8e2a"> <trans-unit id="s84fcddede27b8e2a">
<source>External</source> <source>External</source>
<target>Ēxţēŕńàĺ</target>
</trans-unit> </trans-unit>
<trans-unit id="s1a635369edaf4dc3"> <trans-unit id="s1a635369edaf4dc3">
<source>Service account</source> <source>Service account</source>
<target>Śēŕvĩćē àććōũńţ</target>
</trans-unit> </trans-unit>
<trans-unit id="sff930bf2834e2201"> <trans-unit id="sff930bf2834e2201">
<source>Service account (internal)</source> <source>Service account (internal)</source>
<target>Śēŕvĩćē àććōũńţ (ĩńţēŕńàĺ)</target>
</trans-unit> </trans-unit>
<trans-unit id="s66313b45b69cfc88"> <trans-unit id="s66313b45b69cfc88">
<source>Check the release notes</source> <source>Check the release notes</source>
<target>Ćĥēćķ ţĥē ŕēĺēàśē ńōţēś</target>
</trans-unit> </trans-unit>
<trans-unit id="sb4d7bae2440d9781"> <trans-unit id="sb4d7bae2440d9781">
<source>User Statistics</source> <source>User Statistics</source>
<target>Ũśēŕ Śţàţĩśţĩćś</target>
</trans-unit> </trans-unit>
<trans-unit id="s0924f51b028233a3"> <trans-unit id="s0924f51b028233a3">
<source>&lt;No name set&gt;</source> <source>&lt;No name set&gt;</source>
<target>&lt;Ńō ńàmē śēţ&gt;</target>
</trans-unit> </trans-unit>
<trans-unit id="sdc9a6ad1af30572c"> <trans-unit id="sdc9a6ad1af30572c">
<source>For nginx's auth_request or traefik's forwardAuth</source> <source>For nginx's auth_request or traefik's forwardAuth</source>
<target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩķ'ś ƒōŕŵàŕďÀũţĥ</target>
</trans-unit> </trans-unit>
<trans-unit id="sfc31264ef7ff86ef"> <trans-unit id="sfc31264ef7ff86ef">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source> <source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
<target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩķ'ś ƒōŕŵàŕďÀũţĥ ƥēŕ ŕōōţ ďōmàĩń</target>
</trans-unit> </trans-unit>
<trans-unit id="sc615309d10a9228c"> <trans-unit id="sc615309d10a9228c">
<source>RBAC is in preview.</source> <source>RBAC is in preview.</source>
<target>ŔßÀĆ ĩś ĩń ƥŕēvĩēŵ.</target>
</trans-unit> </trans-unit>
<trans-unit id="s32babfed740fd3c1"> <trans-unit id="s32babfed740fd3c1">
<source>User type used for newly created users.</source> <source>User type used for newly created users.</source>
<target>Ũśēŕ ţŷƥē ũśēď ƒōŕ ńēŵĺŷ ćŕēàţēď ũśēŕś.</target>
</trans-unit> </trans-unit>
<trans-unit id="s4a34a6be4c68ec87"> <trans-unit id="s4a34a6be4c68ec87">
<source>Users created</source> <source>Users created</source>
<target>Ũśēŕś ćŕēàţēď</target>
</trans-unit> </trans-unit>
<trans-unit id="s275c956687e2e656"> <trans-unit id="s275c956687e2e656">
<source>Failed logins</source> <source>Failed logins</source>
<target>Ƒàĩĺēď ĺōĝĩńś</target>
</trans-unit> </trans-unit>
<trans-unit id="sb35c08e3a541188f"> <trans-unit id="sb35c08e3a541188f">
<source>Also known as Client ID.</source> <source>Also known as Client ID.</source>
<target>Àĺśō ķńōŵń àś Ćĺĩēńţ ĨĎ.</target>
</trans-unit> </trans-unit>
<trans-unit id="sd46fd9b647cfea10"> <trans-unit id="sd46fd9b647cfea10">
<source>Also known as Client Secret.</source> <source>Also known as Client Secret.</source>
<target>Àĺśō ķńōŵń àś Ćĺĩēńţ Śēćŕēţ.</target>
</trans-unit> </trans-unit>
<trans-unit id="s4476e9c50cfd13f4"> <trans-unit id="s4476e9c50cfd13f4">
<source>Global status</source> <source>Global status</source>
<target>Ĝĺōƀàĺ śţàţũś</target>
</trans-unit> </trans-unit>
<trans-unit id="sd21a971eea208533"> <trans-unit id="sd21a971eea208533">
<source>Vendor</source> <source>Vendor</source>
<target>Vēńďōŕ</target>
</trans-unit> </trans-unit>
<trans-unit id="sadadfe9dfa06d7dd"> <trans-unit id="sadadfe9dfa06d7dd">
<source>No sync status.</source> <source>No sync status.</source>
<target>Ńō śŷńć śţàţũś.</target>
</trans-unit> </trans-unit>
<trans-unit id="s2b1c81130a65a55b"> <trans-unit id="s2b1c81130a65a55b">
<source>Sync currently running.</source> <source>Sync currently running.</source>
<target>Śŷńć ćũŕŕēńţĺŷ ŕũńńĩńĝ.</target>
</trans-unit> </trans-unit>
<trans-unit id="sf36170f71cea38c2"> <trans-unit id="sf36170f71cea38c2">
<source>Connectivity</source> <source>Connectivity</source>
<target>Ćōńńēćţĩvĩţŷ</target>
</trans-unit> </trans-unit>
<trans-unit id="sd94e99af8b41ff54"> <trans-unit id="sd94e99af8b41ff54">
<source>0: Too guessable: risky password. (guesses &amp;lt; 10^3)</source> <source>0: Too guessable: risky password. (guesses &amp;lt; 10^3)</source>
<target>0: Ţōō ĝũēśśàƀĺē: ŕĩśķŷ ƥàśśŵōŕď. (ĝũēśśēś &amp;ĺţ; 10^3)</target>
</trans-unit> </trans-unit>
<trans-unit id="sc926385d1a624c3a"> <trans-unit id="sc926385d1a624c3a">
<source>1: Very guessable: protection from throttled online attacks. (guesses &amp;lt; 10^6)</source> <source>1: Very guessable: protection from throttled online attacks. (guesses &amp;lt; 10^6)</source>
<target>1: Vēŕŷ ĝũēśśàƀĺē: ƥŕōţēćţĩōń ƒŕōm ţĥŕōţţĺēď ōńĺĩńē àţţàćķś. (ĝũēśśēś &amp;ĺţ; 10^6)</target>
</trans-unit> </trans-unit>
<trans-unit id="s8aae61c41319602c"> <trans-unit id="s8aae61c41319602c">
<source>2: Somewhat guessable: protection from unthrottled online attacks. (guesses &amp;lt; 10^8)</source> <source>2: Somewhat guessable: protection from unthrottled online attacks. (guesses &amp;lt; 10^8)</source>
<target>2: Śōmēŵĥàţ ĝũēśśàƀĺē: ƥŕōţēćţĩōń ƒŕōm ũńţĥŕōţţĺēď ōńĺĩńē àţţàćķś. (ĝũēśśēś &amp;ĺţ; 10^8)</target>
</trans-unit> </trans-unit>
<trans-unit id="sc1f4b57e722a89d6"> <trans-unit id="sc1f4b57e722a89d6">
<source>3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses &amp;lt; 10^10)</source> <source>3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses &amp;lt; 10^10)</source>
<target>3: Śàƒēĺŷ ũńĝũēśśàƀĺē: mōďēŕàţē ƥŕōţēćţĩōń ƒŕōm ōƒƒĺĩńē śĺōŵ-ĥàśĥ śćēńàŕĩō. (ĝũēśśēś &amp;ĺţ; 10^10)</target>
</trans-unit> </trans-unit>
<trans-unit id="sd47f3d3c9741343d"> <trans-unit id="sd47f3d3c9741343d">
<source>4: Very unguessable: strong protection from offline slow-hash scenario. (guesses &amp;gt;= 10^10)</source> <source>4: Very unguessable: strong protection from offline slow-hash scenario. (guesses &amp;gt;= 10^10)</source>
<target>4: Vēŕŷ ũńĝũēśśàƀĺē: śţŕōńĝ ƥŕōţēćţĩōń ƒŕōm ōƒƒĺĩńē śĺōŵ-ĥàśĥ śćēńàŕĩō. (ĝũēśśēś &amp;ĝţ;= 10^10)</target>
</trans-unit> </trans-unit>
<trans-unit id="s3d2a8b86a4f5a810"> <trans-unit id="s3d2a8b86a4f5a810">
<source>Successfully created user and added to group <x id="0" equiv-text="${this.group.name}"/></source> <source>Successfully created user and added to group <x id="0" equiv-text="${this.group.name}"/></source>
<target>Śũććēśśƒũĺĺŷ ćŕēàţēď ũśēŕ àńď àďďēď ţō ĝŕōũƥ <x id="0" equiv-text="${this.group.name}"/></target>
</trans-unit> </trans-unit>
<trans-unit id="s824e0943a7104668"> <trans-unit id="s824e0943a7104668">
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source> <source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
<target>Ţĥĩś ũśēŕ ŵĩĺĺ ƀē àďďēď ţō ţĥē ĝŕōũƥ "<x id="0" equiv-text="${this.targetGroup.name}"/>".</target>
</trans-unit> </trans-unit>
<trans-unit id="s62e7f6ed7d9cb3ca"> <trans-unit id="s62e7f6ed7d9cb3ca">
<source>Pretend user exists</source> <source>Pretend user exists</source>
<target>Ƥŕēţēńď ũśēŕ ēxĩśţś</target>
</trans-unit> </trans-unit>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
<target>Ŵĥēń ēńàƀĺēď, ţĥē śţàĝē ŵĩĺĺ àĺŵàŷś àććēƥţ ţĥē ĝĩvēń ũśēŕ ĩďēńţĩƒĩēŕ àńď ćōńţĩńũē.</target>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
<target>Ţĥēŕē ŵàś àń ēŕŕōŕ ĩń ţĥē àƥƥĺĩćàţĩōń.</target>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
<target>Ŕēvĩēŵ ţĥē àƥƥĺĩćàţĩōń.</target>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
<target>Ţĥēŕē ŵàś àń ēŕŕōŕ ĩń ţĥē ƥŕōvĩďēŕ.</target>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
<target>Ŕēvĩēŵ ţĥē ƥŕōvĩďēŕ.</target>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
<target>Ţĥēŕē ŵàś àń ēŕŕōŕ</target>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
<target>Ţĥēŕē ŵàś àń ēŕŕōŕ ćŕēàţĩńĝ ţĥē àƥƥĺĩćàţĩōń, ƀũţ ńō ēŕŕōŕ mēśśàĝē ŵàś śēńţ. Ƥĺēàśē ŕēvĩēŵ ţĥē śēŕvēŕ ĺōĝś.</target>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
<target>Ćōńƒĩĝũŕē ĹĎÀƤ Ƥŕōvĩďēŕ</target>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
<target>Ćōńƒĩĝũŕē ŌÀũţĥ2/ŌƥēńĨď Ƥŕōvĩďēŕ</target>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
<target>Ćōńƒĩĝũŕē Ƥŕōxŷ Ƥŕōvĩďēŕ</target>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
<target>ÀďďĩţĩōńàĺŚćōƥēś</target>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
<target>Ćōńƒĩĝũŕē Ŕàďĩũś Ƥŕōvĩďēŕ</target>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
<target>Ćōńƒĩĝũŕē ŚÀMĹ Ƥŕōvĩďēŕ</target>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
<target>Ƥŕōƥēŕţŷ màƥƥĩńĝś ũśēď ƒōŕ ũśēŕ màƥƥĩńĝ.</target>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
<target>Ćōńƒĩĝũŕē ŚĆĨM Ƥŕōvĩďēŕ</target>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
<target>Ƥŕōƥēŕţŷ màƥƥĩńĝś ũśēď ƒōŕ ĝŕōũƥ ćŕēàţĩōń.</target>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body></file></xliff> </body></file></xliff>

View File

@ -5709,12 +5709,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s848288f8c2265aad"> <trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source> <source>Your application has been saved</source>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
</trans-unit> </trans-unit>
@ -5984,6 +5978,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -7619,14 +7619,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Your application has been saved</source> <source>Your application has been saved</source>
<target>您的应用程序已保存</target> <target>您的应用程序已保存</target>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
<target>在应用程序中:</target>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
<target>在提供程序中:</target>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
<target>方法的显示名称。</target> <target>方法的显示名称。</target>
@ -7988,6 +7980,54 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
<target>启用时,此阶段总是会接受指定的用户 ID 并继续。</target> <target>启用时,此阶段总是会接受指定的用户 ID 并继续。</target>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5757,12 +5757,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s848288f8c2265aad"> <trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source> <source>Your application has been saved</source>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
</trans-unit> </trans-unit>
@ -6032,6 +6026,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s52bdc80690a9a8dc"> <trans-unit id="s52bdc80690a9a8dc">
<source>When enabled, the stage will always accept the given user identifier and continue.</source> <source>When enabled, the stage will always accept the given user identifier and continue.</source>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -7555,14 +7555,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Your application has been saved</source> <source>Your application has been saved</source>
<target>已經儲存您的應用程式</target> <target>已經儲存您的應用程式</target>
</trans-unit> </trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
<target>在應用程式:</target>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
<target>在供應商:</target>
</trans-unit>
<trans-unit id="s67d858051b34c38b"> <trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source> <source>Method's display Name.</source>
<target>方法的顯示名稱。</target> <target>方法的顯示名稱。</target>
@ -7925,6 +7917,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s5d7748b1d2363478"> <trans-unit id="s5d7748b1d2363478">
<source>Are you sure you want to remove the selected users from the group <x id="0" equiv-text="${this.targetGroup?.name}"/>?</source> <source>Are you sure you want to remove the selected users from the group <x id="0" equiv-text="${this.targetGroup?.name}"/>?</source>
</trans-unit>
<trans-unit id="scda8dc24b561e205">
<source>There was an error in the application.</source>
</trans-unit>
<trans-unit id="sdaca9c2c0361ed3a">
<source>Review the application.</source>
</trans-unit>
<trans-unit id="sb50000a8fada5672">
<source>There was an error in the provider.</source>
</trans-unit>
<trans-unit id="s21f95eaf151d4ce3">
<source>Review the provider.</source>
</trans-unit>
<trans-unit id="s9fd39a5cb20b4e61">
<source>There was an error</source>
</trans-unit>
<trans-unit id="s7a6b3453209e1066">
<source>There was an error creating the application, but no error message was sent. Please review the server logs.</source>
</trans-unit>
<trans-unit id="s1a711c19cda48375">
<source>Configure LDAP Provider</source>
</trans-unit>
<trans-unit id="s9368e965b5c292ab">
<source>Configure OAuth2/OpenId Provider</source>
</trans-unit>
<trans-unit id="sf5cbccdc6254c8dc">
<source>Configure Proxy Provider</source>
</trans-unit>
<trans-unit id="sf6d46bb442b77e91">
<source>AdditionalScopes</source>
</trans-unit>
<trans-unit id="s2c8c6f89089b31d4">
<source>Configure Radius Provider</source>
</trans-unit>
<trans-unit id="sfe906cde5dddc041">
<source>Configure SAML Provider</source>
</trans-unit>
<trans-unit id="sb3defbacd01ad972">
<source>Property mappings used for user mapping.</source>
</trans-unit>
<trans-unit id="s7ccce0ec8d228db6">
<source>Configure SCIM Provider</source>
</trans-unit>
<trans-unit id="sd7728d2b6e1d25e9">
<source>Property mappings used for group creation.</source>
</trans-unit>
<trans-unit id="s7513372fe60f6387">
<source>Event volume</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5,7 +5,7 @@ title: Docker Compose installation
This installation method is for test-setups and small-scale production setups. This installation method is for test-setups and small-scale production setups.
:::info :::info
You can also [view a video walk-through](https://youtu.be/owk1a_1xYe4) of the installation process on Docker (with bonus details about email configuration and other important options). You can also [view a video walk-through](https://www.youtube.com/watch?v=O1qUbrk4Yc8) of the installation process on Docker (with bonus details about email configuration and other important options).
::: :::
## Requirements ## Requirements

View File

@ -5,7 +5,7 @@ title: Kubernetes installation
You can install authentik to run on Kubernetes using Helm Chart. You can install authentik to run on Kubernetes using Helm Chart.
:::info :::info
You can also [view a video walk-through](https://youtu.be/owk1a_1xYe4) of the installation process on Kubernetes (with bonus details about email configuration and other important options). You can also [view a video walk-through](https://www.youtube.com/watch?v=O1qUbrk4Yc8) of the installation process on Kubernetes (with bonus details about email configuration and other important options).
::: :::
### Requirements ### Requirements

View File

@ -18,7 +18,7 @@
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"disqus-react": "^1.1.5", "disqus-react": "^1.1.5",
"postcss": "^8.4.31", "postcss": "^8.4.32",
"prism-react-renderer": "^2.3.0", "prism-react-renderer": "^2.3.0",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"react": "^18.2.0", "react": "^18.2.0",
@ -26,14 +26,14 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-feather": "^2.0.10", "react-feather": "^2.0.10",
"react-toggle": "^4.1.3", "react-toggle": "^4.1.3",
"react-tooltip": "^5.24.0", "react-tooltip": "^5.25.0",
"remark-github": "^12.0.0" "remark-github": "^12.0.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "3.0.1", "@docusaurus/module-type-aliases": "3.0.1",
"@docusaurus/tsconfig": "3.0.1", "@docusaurus/tsconfig": "3.0.1",
"@docusaurus/types": "3.0.1", "@docusaurus/types": "3.0.1",
"@types/react": "^18.2.39", "@types/react": "^18.2.42",
"prettier": "3.1.0", "prettier": "3.1.0",
"typescript": "~5.3.2" "typescript": "~5.3.2"
}, },
@ -4373,9 +4373,9 @@
"integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA=="
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.2.39", "version": "18.2.42",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.42.tgz",
"integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", "integrity": "sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@ -13137,9 +13137,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.31", "version": "8.4.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -13155,7 +13155,7 @@
} }
], ],
"dependencies": { "dependencies": {
"nanoid": "^3.3.6", "nanoid": "^3.3.7",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
@ -14365,9 +14365,9 @@
} }
}, },
"node_modules/react-tooltip": { "node_modules/react-tooltip": {
"version": "5.24.0", "version": "5.25.0",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.24.0.tgz", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.25.0.tgz",
"integrity": "sha512-HjstgpOrUwP4eN6mHU4EThpbxVuKO5SvqumRt1aAcPq0ya+pIVVxlwltndtdIIMBJ7w3jnN05vNfcfh2sxE2mQ==", "integrity": "sha512-/eGhmlwbHlJrVoUe75fb58rJfAy9aZnTvQAK9ZUPM0n9mmBGpEk13vDPiQVCeUuax+fBej+7JPsUXlhzaySc7w==",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.0.0", "@floating-ui/dom": "^1.0.0",
"classnames": "^2.3.0" "classnames": "^2.3.0"

View File

@ -25,14 +25,14 @@
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"disqus-react": "^1.1.5", "disqus-react": "^1.1.5",
"postcss": "^8.4.31", "postcss": "^8.4.32",
"prism-react-renderer": "^2.3.0", "prism-react-renderer": "^2.3.0",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"react-before-after-slider-component": "^1.1.8", "react-before-after-slider-component": "^1.1.8",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-feather": "^2.0.10", "react-feather": "^2.0.10",
"react-toggle": "^4.1.3", "react-toggle": "^4.1.3",
"react-tooltip": "^5.24.0", "react-tooltip": "^5.25.0",
"react": "^18.2.0", "react": "^18.2.0",
"remark-github": "^12.0.0" "remark-github": "^12.0.0"
}, },
@ -52,7 +52,7 @@
"@docusaurus/module-type-aliases": "3.0.1", "@docusaurus/module-type-aliases": "3.0.1",
"@docusaurus/tsconfig": "3.0.1", "@docusaurus/tsconfig": "3.0.1",
"@docusaurus/types": "3.0.1", "@docusaurus/types": "3.0.1",
"@types/react": "^18.2.39", "@types/react": "^18.2.42",
"prettier": "3.1.0", "prettier": "3.1.0",
"typescript": "~5.3.2" "typescript": "~5.3.2"
}, },