diff --git a/.github/codecov.yml b/.github/codecov.yml index 1042659ed..8db67faf6 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -6,5 +6,5 @@ coverage: # adjust accordingly based on how flaky your tests are # this allows a 1% drop from the previous base commit coverage threshold: 1% - notify: - after_n_builds: 3 +comment: + after_n_builds: 3 diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml index d24a2a88d..097321888 100644 --- a/.github/workflows/ci-main.yml +++ b/.github/workflows/ci-main.yml @@ -90,6 +90,7 @@ jobs: psql: - 12-alpine - 15-alpine + - 16-alpine steps: - uses: actions/checkout@v4 - name: Setup authentik env diff --git a/Makefile b/Makefile index bb7f70a43..07a84fb70 100644 --- a/Makefile +++ b/Makefile @@ -56,14 +56,15 @@ test: ## Run the server tests and produce a coverage report (locally) coverage report lint-fix: ## Lint and automatically fix errors in the python source code. Reports spelling errors. - isort authentik $(PY_SOURCES) - black authentik $(PY_SOURCES) - ruff authentik $(PY_SOURCES) + isort $(PY_SOURCES) + black $(PY_SOURCES) + ruff $(PY_SOURCES) codespell -w $(CODESPELL_ARGS) lint: ## Lint the python and golang sources - pylint $(PY_SOURCES) bandit -r $(PY_SOURCES) -x node_modules + ./web/node_modules/.bin/pyright $(PY_SOURCES) + pylint $(PY_SOURCES) golangci-lint run -v migrate: ## Run the Authentik Django server's migrations diff --git a/authentik/admin/api/meta.py b/authentik/admin/api/meta.py index 25d944411..52640b8c5 100644 --- a/authentik/admin/api/meta.py +++ b/authentik/admin/api/meta.py @@ -1,7 +1,7 @@ """Meta API""" from drf_spectacular.utils import extend_schema from rest_framework.fields import CharField -from rest_framework.permissions import IsAdminUser +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import ViewSet @@ -21,7 +21,7 @@ class AppSerializer(PassiveSerializer): class AppsViewSet(ViewSet): """Read-only view list all installed apps""" - permission_classes = [IsAdminUser] + permission_classes = [IsAuthenticated] @extend_schema(responses={200: AppSerializer(many=True)}) def list(self, request: Request) -> Response: @@ -35,7 +35,7 @@ class AppsViewSet(ViewSet): class ModelViewSet(ViewSet): """Read-only view list all installed models""" - permission_classes = [IsAdminUser] + permission_classes = [IsAuthenticated] @extend_schema(responses={200: AppSerializer(many=True)}) def list(self, request: Request) -> Response: diff --git a/authentik/admin/api/metrics.py b/authentik/admin/api/metrics.py index 08aea59d2..af32662b1 100644 --- a/authentik/admin/api/metrics.py +++ b/authentik/admin/api/metrics.py @@ -5,7 +5,7 @@ from django.db.models.functions import ExtractHour from drf_spectacular.utils import extend_schema, extend_schema_field from guardian.shortcuts import get_objects_for_user from rest_framework.fields import IntegerField, SerializerMethodField -from rest_framework.permissions import IsAdminUser +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -68,7 +68,7 @@ class LoginMetricsSerializer(PassiveSerializer): class AdministrationMetricsViewSet(APIView): """Login Metrics per 1h""" - permission_classes = [IsAdminUser] + permission_classes = [IsAuthenticated] @extend_schema(responses={200: LoginMetricsSerializer(many=False)}) def get(self, request: Request) -> Response: diff --git a/authentik/admin/api/system.py b/authentik/admin/api/system.py index 11dc5dfec..7e7d2d920 100644 --- a/authentik/admin/api/system.py +++ b/authentik/admin/api/system.py @@ -8,7 +8,6 @@ from django.utils.timezone import now from drf_spectacular.utils import extend_schema from gunicorn import version_info as gunicorn_version from rest_framework.fields import SerializerMethodField -from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -17,6 +16,7 @@ from authentik.core.api.utils import PassiveSerializer from authentik.lib.utils.reflection import get_env from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.models import Outpost +from authentik.rbac.permissions import HasPermission class RuntimeDict(TypedDict): @@ -88,7 +88,7 @@ class SystemSerializer(PassiveSerializer): class SystemView(APIView): """Get system information.""" - permission_classes = [IsAdminUser] + permission_classes = [HasPermission("authentik_rbac.view_system_info")] pagination_class = None filter_backends = [] serializer_class = SystemSerializer diff --git a/authentik/admin/api/tasks.py b/authentik/admin/api/tasks.py index 00fbe4e08..72714dad5 100644 --- a/authentik/admin/api/tasks.py +++ b/authentik/admin/api/tasks.py @@ -14,14 +14,15 @@ from rest_framework.fields import ( ListField, SerializerMethodField, ) -from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import ViewSet from structlog.stdlib import get_logger +from authentik.api.decorators import permission_required from authentik.core.api.utils import PassiveSerializer from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus +from authentik.rbac.permissions import HasPermission LOGGER = get_logger() @@ -63,7 +64,7 @@ class TaskSerializer(PassiveSerializer): class TaskViewSet(ViewSet): """Read-only view set that returns all background tasks""" - permission_classes = [IsAdminUser] + permission_classes = [HasPermission("authentik_rbac.view_system_tasks")] serializer_class = TaskSerializer @extend_schema( @@ -93,6 +94,7 @@ class TaskViewSet(ViewSet): tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name) return Response(TaskSerializer(tasks, many=True).data) + @permission_required(None, ["authentik_rbac.run_system_tasks"]) @extend_schema( request=OpenApiTypes.NONE, responses={ diff --git a/authentik/admin/api/workers.py b/authentik/admin/api/workers.py index ab6d03873..3b5da0594 100644 --- a/authentik/admin/api/workers.py +++ b/authentik/admin/api/workers.py @@ -2,18 +2,18 @@ from django.conf import settings from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework.fields import IntegerField -from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +from authentik.rbac.permissions import HasPermission from authentik.root.celery import CELERY_APP class WorkerView(APIView): """Get currently connected worker count.""" - permission_classes = [IsAdminUser] + permission_classes = [HasPermission("authentik_rbac.view_system_info")] @extend_schema(responses=inline_serializer("Workers", fields={"count": IntegerField()})) def get(self, request: Request) -> Response: diff --git a/authentik/api/authorization.py b/authentik/api/authorization.py index 05cd45819..e3ae48e5c 100644 --- a/authentik/api/authorization.py +++ b/authentik/api/authorization.py @@ -7,9 +7,9 @@ from rest_framework.authentication import get_authorization_header from rest_framework.filters import BaseFilterBackend from rest_framework.permissions import BasePermission from rest_framework.request import Request -from rest_framework_guardian.filters import ObjectPermissionsFilter from authentik.api.authentication import validate_auth +from authentik.rbac.filters import ObjectFilter class OwnerFilter(BaseFilterBackend): @@ -26,14 +26,14 @@ class OwnerFilter(BaseFilterBackend): class SecretKeyFilter(DjangoFilterBackend): """Allow access to all objects when authenticated with secret key as token. - Replaces both DjangoFilterBackend and ObjectPermissionsFilter""" + Replaces both DjangoFilterBackend and ObjectFilter""" def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet: auth_header = get_authorization_header(request) token = validate_auth(auth_header) if token and token == settings.SECRET_KEY: return queryset - queryset = ObjectPermissionsFilter().filter_queryset(request, queryset, view) + queryset = ObjectFilter().filter_queryset(request, queryset, view) return super().filter_queryset(request, queryset, view) diff --git a/authentik/api/decorators.py b/authentik/api/decorators.py index a79cebc92..0cd737c76 100644 --- a/authentik/api/decorators.py +++ b/authentik/api/decorators.py @@ -10,7 +10,7 @@ from structlog.stdlib import get_logger LOGGER = get_logger() -def permission_required(perm: Optional[str] = None, other_perms: Optional[list[str]] = None): +def permission_required(obj_perm: Optional[str] = None, global_perms: Optional[list[str]] = None): """Check permissions for a single custom action""" def wrapper_outter(func: Callable): @@ -18,15 +18,17 @@ def permission_required(perm: Optional[str] = None, other_perms: Optional[list[s @wraps(func) def wrapper(self: ModelViewSet, request: Request, *args, **kwargs) -> Response: - if perm: + if obj_perm: obj = self.get_object() - if not request.user.has_perm(perm, obj): - LOGGER.debug("denying access for object", user=request.user, perm=perm, obj=obj) + if not request.user.has_perm(obj_perm, obj): + LOGGER.debug( + "denying access for object", user=request.user, perm=obj_perm, obj=obj + ) return self.permission_denied(request) - if other_perms: - for other_perm in other_perms: + if global_perms: + for other_perm in global_perms: if not request.user.has_perm(other_perm): - LOGGER.debug("denying access for other", user=request.user, perm=perm) + LOGGER.debug("denying access for other", user=request.user, perm=other_perm) return self.permission_denied(request) return func(self, request, *args, **kwargs) diff --git a/authentik/api/pagination.py b/authentik/api/pagination.py index 7125c8968..402dbac9b 100644 --- a/authentik/api/pagination.py +++ b/authentik/api/pagination.py @@ -77,3 +77,10 @@ class Pagination(pagination.PageNumberPagination): }, "required": ["pagination", "results"], } + + +class SmallerPagination(Pagination): + """Smaller pagination for objects which might require a lot of queries + to retrieve all data for.""" + + max_page_size = 10 diff --git a/authentik/api/tests/test_viewsets.py b/authentik/api/tests/test_viewsets.py index dee956461..ac3d7da62 100644 --- a/authentik/api/tests/test_viewsets.py +++ b/authentik/api/tests/test_viewsets.py @@ -16,6 +16,7 @@ def viewset_tester_factory(test_viewset: type[ModelViewSet]) -> Callable: def tester(self: TestModelViewSets): self.assertIsNotNone(getattr(test_viewset, "search_fields", None)) + self.assertIsNotNone(getattr(test_viewset, "ordering", None)) filterset_class = getattr(test_viewset, "filterset_class", None) if not filterset_class: self.assertIsNotNone(getattr(test_viewset, "filterset_fields", None)) diff --git a/authentik/blueprints/api.py b/authentik/blueprints/api.py index 9fac62c72..721eb5dcb 100644 --- a/authentik/blueprints/api.py +++ b/authentik/blueprints/api.py @@ -4,7 +4,6 @@ from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework.decorators import action from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, DateTimeField, JSONField -from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ListSerializer, ModelSerializer @@ -87,11 +86,11 @@ class BlueprintInstanceSerializer(ModelSerializer): class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet): """Blueprint instances""" - permission_classes = [IsAdminUser] serializer_class = BlueprintInstanceSerializer queryset = BlueprintInstance.objects.all() search_fields = ["name", "path"] filterset_fields = ["name", "path"] + ordering = ["name"] @extend_schema( responses={ diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py index 76c667c25..f2191548e 100644 --- a/authentik/blueprints/v1/importer.py +++ b/authentik/blueprints/v1/importer.py @@ -35,25 +35,28 @@ from authentik.core.models import ( Source, UserSourceConnection, ) +from authentik.enterprise.models import LicenseUsage from authentik.events.utils import cleanse_dict from authentik.flows.models import FlowToken, Stage from authentik.lib.models import SerializerModel from authentik.lib.sentry import SentryIgnoredException from authentik.outposts.models import OutpostServiceConnection from authentik.policies.models import Policy, PolicyBindingModel +from authentik.providers.scim.models import SCIMGroup, SCIMUser # Context set when the serializer is created in a blueprint context # Update website/developer-docs/blueprints/v1/models.md when used SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry" -def is_model_allowed(model: type[Model]) -> bool: - """Check if model is allowed""" +def excluded_models() -> list[type[Model]]: + """Return a list of all excluded models that shouldn't be exposed via API + or other means (internal only, base classes, non-used objects, etc)""" # pylint: disable=imported-auth-user from django.contrib.auth.models import Group as DjangoGroup from django.contrib.auth.models import User as DjangoUser - excluded_models = ( + return ( DjangoUser, DjangoGroup, # Base classes @@ -69,8 +72,15 @@ def is_model_allowed(model: type[Model]) -> bool: AuthenticatedSession, # Classes which are only internally managed FlowToken, + LicenseUsage, + SCIMGroup, + SCIMUser, ) - return model not in excluded_models and issubclass(model, (SerializerModel, BaseMetaModel)) + + +def is_model_allowed(model: type[Model]) -> bool: + """Check if model is allowed""" + return model not in excluded_models() and issubclass(model, (SerializerModel, BaseMetaModel)) class DoRollback(SentryIgnoredException): diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index f40aa3165..478181c28 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -17,7 +17,6 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet -from rest_framework_guardian.filters import ObjectPermissionsFilter from structlog.stdlib import get_logger from structlog.testing import capture_logs @@ -38,6 +37,7 @@ from authentik.lib.utils.file import ( from authentik.policies.api.exec import PolicyTestResultSerializer from authentik.policies.engine import PolicyEngine from authentik.policies.types import PolicyResult +from authentik.rbac.filters import ObjectFilter LOGGER = get_logger() @@ -122,7 +122,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet: """Custom filter_queryset method which ignores guardian, but still supports sorting""" for backend in list(self.filter_backends): - if backend == ObjectPermissionsFilter: + if backend == ObjectFilter: continue queryset = backend().filter_queryset(self.request, queryset, self) return queryset diff --git a/authentik/core/api/groups.py b/authentik/core/api/groups.py index 961633037..4c6a8b509 100644 --- a/authentik/core/api/groups.py +++ b/authentik/core/api/groups.py @@ -2,7 +2,6 @@ from json import loads from typing import Optional -from django.db.models.query import QuerySet from django.http import Http404 from django_filters.filters import CharFilter, ModelMultipleChoiceFilter from django_filters.filterset import FilterSet @@ -14,12 +13,12 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError from rest_framework.viewsets import ModelViewSet -from rest_framework_guardian.filters import ObjectPermissionsFilter from authentik.api.decorators import permission_required from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.models import Group, User +from authentik.rbac.api.roles import RoleSerializer class GroupMemberSerializer(ModelSerializer): @@ -49,6 +48,12 @@ class GroupSerializer(ModelSerializer): users_obj = ListSerializer( child=GroupMemberSerializer(), read_only=True, source="users", required=False ) + roles_obj = ListSerializer( + child=RoleSerializer(), + read_only=True, + source="roles", + required=False, + ) parent_name = CharField(source="parent.name", read_only=True, allow_null=True) num_pk = IntegerField(read_only=True) @@ -71,8 +76,10 @@ class GroupSerializer(ModelSerializer): "parent", "parent_name", "users", - "attributes", "users_obj", + "attributes", + "roles", + "roles_obj", ] extra_kwargs = { "users": { @@ -138,19 +145,6 @@ class GroupViewSet(UsedByMixin, ModelViewSet): filterset_class = GroupFilter ordering = ["name"] - def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet: - """Custom filter_queryset method which ignores guardian, but still supports sorting""" - for backend in list(self.filter_backends): - if backend == ObjectPermissionsFilter: - continue - queryset = backend().filter_queryset(self.request, queryset, self) - return queryset - - def filter_queryset(self, queryset): - if self.request.user.has_perm("authentik_core.view_group"): - return self._filter_queryset_for_list(queryset) - return super().filter_queryset(queryset) - @permission_required(None, ["authentik_core.add_user"]) @extend_schema( request=UserAccountSerializer, diff --git a/authentik/core/api/transactional_applications.py b/authentik/core/api/transactional_applications.py index 9cc0ab0e5..19b6ea465 100644 --- a/authentik/core/api/transactional_applications.py +++ b/authentik/core/api/transactional_applications.py @@ -119,6 +119,7 @@ class TransactionApplicationResponseSerializer(PassiveSerializer): class TransactionalApplicationView(APIView): """Create provider and application and attach them in a single transaction""" + # TODO: Migrate to a more specific permission permission_classes = [IsAdminUser] @extend_schema( diff --git a/authentik/core/api/used_by.py b/authentik/core/api/used_by.py index 66f0a70dd..25e7d8295 100644 --- a/authentik/core/api/used_by.py +++ b/authentik/core/api/used_by.py @@ -73,6 +73,11 @@ class UsedByMixin: # but so we only apply them once, have a simple flag for the first object first_object = True + # TODO: This will only return the used-by references that the user can see + # Either we have to leak model information here to not make the list + # useless if the user doesn't have all permissions, or we need to double + # query and check if there is a difference between modes the user can see + # and can't see and add a warning for obj in get_objects_for_user( request.user, f"{app}.view_{model_name}", manager ).all(): diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index be59dc1c1..d4adacc97 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -7,7 +7,6 @@ from django.contrib.auth import update_session_auth_hash from django.contrib.sessions.backends.cache import KEY_PREFIX from django.core.cache import cache from django.db.models.functions import ExtractHour -from django.db.models.query import QuerySet from django.db.transaction import atomic from django.db.utils import IntegrityError from django.urls import reverse_lazy @@ -52,7 +51,6 @@ from rest_framework.serializers import ( ) from rest_framework.validators import UniqueValidator from rest_framework.viewsets import ModelViewSet -from rest_framework_guardian.filters import ObjectPermissionsFilter from structlog.stdlib import get_logger from authentik.admin.api.metrics import CoordinateSerializer @@ -205,6 +203,7 @@ class UserSelfSerializer(ModelSerializer): groups = SerializerMethodField() uid = CharField(read_only=True) settings = SerializerMethodField() + system_permissions = SerializerMethodField() @extend_schema_field( ListSerializer( @@ -226,6 +225,14 @@ class UserSelfSerializer(ModelSerializer): """Get user settings with tenant and group settings applied""" return user.group_attributes(self._context["request"]).get("settings", {}) + def get_system_permissions(self, user: User) -> list[str]: + """Get all system permissions assigned to the user""" + return list( + user.user_permissions.filter( + content_type__app_label="authentik_rbac", content_type__model="systempermission" + ).values_list("codename", flat=True) + ) + class Meta: model = User fields = [ @@ -240,6 +247,7 @@ class UserSelfSerializer(ModelSerializer): "uid", "settings", "type", + "system_permissions", ] extra_kwargs = { "is_active": {"read_only": True}, @@ -654,19 +662,6 @@ class UserViewSet(UsedByMixin, ModelViewSet): return Response(status=204) - def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet: - """Custom filter_queryset method which ignores guardian, but still supports sorting""" - for backend in list(self.filter_backends): - if backend == ObjectPermissionsFilter: - continue - queryset = backend().filter_queryset(self.request, queryset, self) - return queryset - - def filter_queryset(self, queryset): - if self.request.user.has_perm("authentik_core.view_user"): - return self._filter_queryset_for_list(queryset) - return super().filter_queryset(queryset) - @extend_schema( responses={ 200: inline_serializer( diff --git a/authentik/core/migrations/0032_group_roles.py b/authentik/core/migrations/0032_group_roles.py new file mode 100644 index 000000000..754b1bfba --- /dev/null +++ b/authentik/core/migrations/0032_group_roles.py @@ -0,0 +1,45 @@ +# Generated by Django 4.2.6 on 2023-10-11 13:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_core", "0031_alter_user_type"), + ("authentik_rbac", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="group", + options={"verbose_name": "Group", "verbose_name_plural": "Groups"}, + ), + migrations.AlterModelOptions( + name="token", + options={ + "permissions": [("view_token_key", "View token's key")], + "verbose_name": "Token", + "verbose_name_plural": "Tokens", + }, + ), + migrations.AlterModelOptions( + name="user", + options={ + "permissions": [ + ("reset_user_password", "Reset Password"), + ("impersonate", "Can impersonate other users"), + ("assign_user_permissions", "Can assign permissions to users"), + ("unassign_user_permissions", "Can unassign permissions from users"), + ], + "verbose_name": "User", + "verbose_name_plural": "Users", + }, + ), + migrations.AddField( + model_name="group", + name="roles", + field=models.ManyToManyField( + blank=True, related_name="ak_groups", to="authentik_rbac.role" + ), + ), + ] diff --git a/authentik/core/models.py b/authentik/core/models.py index 7f4c25ca7..5365ef693 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -1,7 +1,7 @@ """authentik core models""" from datetime import timedelta from hashlib import sha256 -from typing import Any, Optional +from typing import Any, Optional, Self from uuid import uuid4 from deepmerge import always_merger @@ -88,6 +88,8 @@ class Group(SerializerModel): default=False, help_text=_("Users added to this group will be superusers.") ) + roles = models.ManyToManyField("authentik_rbac.Role", related_name="ak_groups", blank=True) + parent = models.ForeignKey( "Group", blank=True, @@ -115,6 +117,38 @@ class Group(SerializerModel): """Recursively check if `user` is member of us, or any parent.""" return user.all_groups().filter(group_uuid=self.group_uuid).exists() + def children_recursive(self: Self | QuerySet["Group"]) -> QuerySet["Group"]: + """Recursively get all groups that have this as parent or are indirectly related""" + direct_groups = [] + if isinstance(self, QuerySet): + direct_groups = list(x for x in self.all().values_list("pk", flat=True).iterator()) + else: + direct_groups = [self.pk] + if len(direct_groups) < 1: + return Group.objects.none() + query = """ + WITH RECURSIVE parents AS ( + SELECT authentik_core_group.*, 0 AS relative_depth + FROM authentik_core_group + WHERE authentik_core_group.group_uuid = ANY(%s) + + UNION ALL + + SELECT authentik_core_group.*, parents.relative_depth + 1 + FROM authentik_core_group, parents + WHERE ( + authentik_core_group.group_uuid = parents.parent_id and + parents.relative_depth < 20 + ) + ) + SELECT group_uuid + FROM parents + GROUP BY group_uuid, name + ORDER BY name; + """ + group_pks = [group.pk for group in Group.objects.raw(query, [direct_groups]).iterator()] + return Group.objects.filter(pk__in=group_pks) + def __str__(self): return f"Group {self.name}" @@ -125,6 +159,8 @@ class Group(SerializerModel): "parent", ), ) + verbose_name = _("Group") + verbose_name_plural = _("Groups") class UserManager(DjangoUserManager): @@ -160,33 +196,7 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): """Recursively get all groups this user is a member of. At least one query is done to get the direct groups of the user, with groups there are at most 3 queries done""" - direct_groups = list( - x for x in self.ak_groups.all().values_list("pk", flat=True).iterator() - ) - if len(direct_groups) < 1: - return Group.objects.none() - query = """ - WITH RECURSIVE parents AS ( - SELECT authentik_core_group.*, 0 AS relative_depth - FROM authentik_core_group - WHERE authentik_core_group.group_uuid = ANY(%s) - - UNION ALL - - SELECT authentik_core_group.*, parents.relative_depth + 1 - FROM authentik_core_group, parents - WHERE ( - authentik_core_group.group_uuid = parents.parent_id and - parents.relative_depth < 20 - ) - ) - SELECT group_uuid - FROM parents - GROUP BY group_uuid, name - ORDER BY name; - """ - group_pks = [group.pk for group in Group.objects.raw(query, [direct_groups]).iterator()] - return Group.objects.filter(pk__in=group_pks) + return Group.children_recursive(self.ak_groups.all()) def group_attributes(self, request: Optional[HttpRequest] = None) -> dict[str, Any]: """Get a dictionary containing the attributes from all groups the user belongs to, @@ -261,12 +271,14 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): return get_avatar(self) class Meta: - permissions = ( - ("reset_user_password", "Reset Password"), - ("impersonate", "Can impersonate other users"), - ) verbose_name = _("User") verbose_name_plural = _("Users") + permissions = [ + ("reset_user_password", _("Reset Password")), + ("impersonate", _("Can impersonate other users")), + ("assign_user_permissions", _("Can assign permissions to users")), + ("unassign_user_permissions", _("Can unassign permissions from users")), + ] class Provider(SerializerModel): @@ -675,7 +687,7 @@ class Token(SerializerModel, ManagedModel, ExpiringModel): models.Index(fields=["identifier"]), models.Index(fields=["key"]), ] - permissions = (("view_token_key", "View token's key"),) + permissions = [("view_token_key", _("View token's key"))] class PropertyMapping(SerializerModel, ManagedModel): diff --git a/authentik/core/signals.py b/authentik/core/signals.py index 76cbd38ff..31340fce9 100644 --- a/authentik/core/signals.py +++ b/authentik/core/signals.py @@ -7,6 +7,7 @@ from django.db.models import Model from django.db.models.signals import post_save, pre_delete, pre_save from django.dispatch import receiver from django.http.request import HttpRequest +from structlog.stdlib import get_logger from authentik.core.models import Application, AuthenticatedSession, BackchannelProvider, User @@ -15,6 +16,8 @@ password_changed = Signal() # Arguments: credentials: dict[str, any], request: HttpRequest, stage: Stage login_failed = Signal() +LOGGER = get_logger() + @receiver(post_save, sender=Application) def post_save_application(sender: type[Model], instance, created: bool, **_): diff --git a/authentik/core/tests/utils.py b/authentik/core/tests/utils.py index 59294e6fd..da4294f42 100644 --- a/authentik/core/tests/utils.py +++ b/authentik/core/tests/utils.py @@ -21,10 +21,9 @@ def create_test_flow( ) -def create_test_admin_user(name: Optional[str] = None, **kwargs) -> User: - """Generate a test-admin user""" +def create_test_user(name: Optional[str] = None, **kwargs) -> User: + """Generate a test user""" uid = generate_id(20) if not name else name - group = Group.objects.create(name=uid, is_superuser=True) kwargs.setdefault("email", f"{uid}@goauthentik.io") kwargs.setdefault("username", uid) user: User = User.objects.create( @@ -33,6 +32,13 @@ def create_test_admin_user(name: Optional[str] = None, **kwargs) -> User: ) user.set_password(uid) user.save() + return user + + +def create_test_admin_user(name: Optional[str] = None, **kwargs) -> User: + """Generate a test-admin user""" + user = create_test_user(name, **kwargs) + group = Group.objects.create(name=user.name or name, is_superuser=True) group.users.add(user) return user diff --git a/authentik/enterprise/api.py b/authentik/enterprise/api.py index 6a215b1fe..fdf0a11fc 100644 --- a/authentik/enterprise/api.py +++ b/authentik/enterprise/api.py @@ -6,7 +6,7 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework.decorators import action from rest_framework.fields import BooleanField, CharField, DateTimeField, IntegerField -from rest_framework.permissions import IsAdminUser, IsAuthenticated +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ModelSerializer @@ -84,7 +84,7 @@ class LicenseViewSet(UsedByMixin, ModelViewSet): 200: inline_serializer("InstallIDSerializer", {"install_id": CharField(required=True)}), }, ) - @action(detail=False, methods=["GET"], permission_classes=[IsAdminUser]) + @action(detail=False, methods=["GET"]) def get_install_id(self, request: Request) -> Response: """Get install_id""" return Response( diff --git a/authentik/enterprise/migrations/0002_rename_users_license_internal_users_and_more.py b/authentik/enterprise/migrations/0002_rename_users_license_internal_users_and_more.py index b3c199743..da574762e 100644 --- a/authentik/enterprise/migrations/0002_rename_users_license_internal_users_and_more.py +++ b/authentik/enterprise/migrations/0002_rename_users_license_internal_users_and_more.py @@ -33,4 +33,8 @@ class Migration(migrations.Migration): "verbose_name_plural": "License Usage Records", }, ), + migrations.AlterModelOptions( + name="license", + options={"verbose_name": "License", "verbose_name_plural": "Licenses"}, + ), ] diff --git a/authentik/enterprise/models.py b/authentik/enterprise/models.py index d10acd3ef..5def8ad37 100644 --- a/authentik/enterprise/models.py +++ b/authentik/enterprise/models.py @@ -19,8 +19,10 @@ from django.utils.translation import gettext as _ from guardian.shortcuts import get_anonymous_user from jwt import PyJWTError, decode, get_unverified_header from rest_framework.exceptions import ValidationError +from rest_framework.serializers import BaseSerializer from authentik.core.models import ExpiringModel, User, UserTypes +from authentik.lib.models import SerializerModel from authentik.root.install_id import get_install_id @@ -134,6 +136,9 @@ class LicenseKey: def record_usage(self): """Capture the current validity status and metrics and save them""" + threshold = now() - timedelta(hours=8) + if LicenseUsage.objects.filter(record_date__gte=threshold).exists(): + return LicenseUsage.objects.create( user_count=self.get_default_user_count(), external_user_count=self.get_external_user_count(), @@ -151,7 +156,7 @@ class LicenseKey: return usage.record_date -class License(models.Model): +class License(SerializerModel): """An authentik enterprise license""" license_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) @@ -162,6 +167,12 @@ class License(models.Model): internal_users = models.BigIntegerField() external_users = models.BigIntegerField() + @property + def serializer(self) -> type[BaseSerializer]: + from authentik.enterprise.api import LicenseSerializer + + return LicenseSerializer + @property def status(self) -> LicenseKey: """Get parsed license status""" @@ -169,6 +180,8 @@ class License(models.Model): class Meta: indexes = (HashIndex(fields=("key",)),) + verbose_name = _("License") + verbose_name_plural = _("Licenses") def usage_expiry(): diff --git a/authentik/enterprise/settings.py b/authentik/enterprise/settings.py index af1da7294..87aaea71b 100644 --- a/authentik/enterprise/settings.py +++ b/authentik/enterprise/settings.py @@ -6,7 +6,7 @@ from authentik.lib.utils.time import fqdn_rand CELERY_BEAT_SCHEDULE = { "enterprise_calculate_license": { "task": "authentik.enterprise.tasks.calculate_license", - "schedule": crontab(minute=fqdn_rand("calculate_license"), hour="*/8"), + "schedule": crontab(minute=fqdn_rand("calculate_license"), hour="*/2"), "options": {"queue": "authentik_scheduled"}, } } diff --git a/authentik/enterprise/tasks.py b/authentik/enterprise/tasks.py index 2931a726b..2063cf07a 100644 --- a/authentik/enterprise/tasks.py +++ b/authentik/enterprise/tasks.py @@ -6,5 +6,4 @@ from authentik.root.celery import CELERY_APP @CELERY_APP.task() def calculate_license(): """Calculate licensing status""" - total = LicenseKey.get_total() - total.record_usage() + LicenseKey.get_total().record_usage() diff --git a/authentik/flows/api/bindings.py b/authentik/flows/api/bindings.py index bda3ab323..96e2e4662 100644 --- a/authentik/flows/api/bindings.py +++ b/authentik/flows/api/bindings.py @@ -45,3 +45,4 @@ class FlowStageBindingViewSet(UsedByMixin, ModelViewSet): serializer_class = FlowStageBindingSerializer filterset_fields = "__all__" search_fields = ["stage__name"] + ordering = ["order"] diff --git a/authentik/flows/challenge.py b/authentik/flows/challenge.py index e1eeb56fd..eb968ce79 100644 --- a/authentik/flows/challenge.py +++ b/authentik/flows/challenge.py @@ -132,13 +132,6 @@ class PermissionDict(TypedDict): name: str -class PermissionSerializer(PassiveSerializer): - """Permission used for consent""" - - name = CharField(allow_blank=True) - id = CharField() - - class ChallengeResponse(PassiveSerializer): """Base class for all challenge responses""" diff --git a/authentik/flows/migrations/0026_alter_flow_options.py b/authentik/flows/migrations/0026_alter_flow_options.py new file mode 100644 index 000000000..c53d3774e --- /dev/null +++ b/authentik/flows/migrations/0026_alter_flow_options.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.6 on 2023-10-10 17:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_flows", "0025_alter_flowstagebinding_evaluate_on_plan_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="flow", + options={ + "permissions": [ + ("export_flow", "Can export a Flow"), + ("inspect_flow", "Can inspect a Flow's execution"), + ("view_flow_cache", "View Flow's cache metrics"), + ("clear_flow_cache", "Clear Flow's cache metrics"), + ], + "verbose_name": "Flow", + "verbose_name_plural": "Flows", + }, + ), + ] diff --git a/authentik/flows/models.py b/authentik/flows/models.py index f638ef04d..67f7f0a9c 100644 --- a/authentik/flows/models.py +++ b/authentik/flows/models.py @@ -194,9 +194,10 @@ class Flow(SerializerModel, PolicyBindingModel): verbose_name_plural = _("Flows") permissions = [ - ("export_flow", "Can export a Flow"), - ("view_flow_cache", "View Flow's cache metrics"), - ("clear_flow_cache", "Clear Flow's cache metrics"), + ("export_flow", _("Can export a Flow")), + ("inspect_flow", _("Can inspect a Flow's execution")), + ("view_flow_cache", _("View Flow's cache metrics")), + ("clear_flow_cache", _("Clear Flow's cache metrics")), ] diff --git a/authentik/flows/views/inspector.py b/authentik/flows/views/inspector.py index 3593c65da..4800ead6d 100644 --- a/authentik/flows/views/inspector.py +++ b/authentik/flows/views/inspector.py @@ -3,6 +3,7 @@ from hashlib import sha256 from typing import Any from django.conf import settings +from django.http import Http404 from django.http.request import HttpRequest from django.http.response import HttpResponse from django.shortcuts import get_object_or_404 @@ -11,7 +12,6 @@ from django.views.decorators.clickjacking import xframe_options_sameorigin from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiResponse, extend_schema from rest_framework.fields import BooleanField, ListField, SerializerMethodField -from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -68,21 +68,19 @@ class FlowInspectionSerializer(PassiveSerializer): class FlowInspectorView(APIView): """Flow inspector API""" - permission_classes = [IsAdminUser] - flow: Flow _logger: BoundLogger - - def check_permissions(self, request): - """Always allow access when in debug mode""" - if settings.DEBUG: - return None - return super().check_permissions(request) + permission_classes = [] def setup(self, request: HttpRequest, flow_slug: str): super().setup(request, flow_slug=flow_slug) - self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug) self._logger = get_logger().bind(flow_slug=flow_slug) + self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug) + if settings.DEBUG: + return + if request.user.has_perm("authentik_flow.inspect_flow", self.flow): + return + raise Http404 @extend_schema( responses={ diff --git a/authentik/lib/validators.py b/authentik/lib/validators.py new file mode 100644 index 000000000..7c67da8c1 --- /dev/null +++ b/authentik/lib/validators.py @@ -0,0 +1,32 @@ +"""Serializer validators""" +from typing import Optional + +from django.utils.translation import gettext_lazy as _ +from rest_framework.exceptions import ValidationError +from rest_framework.serializers import Serializer +from rest_framework.utils.representation import smart_repr + + +class RequiredTogetherValidator: + """Serializer-level validator that ensures all fields in `fields` are only + used together""" + + fields: list[str] + requires_context = True + message = _("The fields {field_names} must be used together.") + + def __init__(self, fields: list[str], message: Optional[str] = None) -> None: + self.fields = fields + self.message = message or self.message + + def __call__(self, attrs: dict, serializer: Serializer): + """Check that if any of the fields in `self.fields` are set, all of them must be set""" + if any(field in attrs for field in self.fields) and not all( + field in attrs for field in self.fields + ): + field_names = ", ".join(self.fields) + message = self.message.format(field_names=field_names) + raise ValidationError(message, code="required") + + def __repr__(self): + return "<%s(fields=%s)>" % (self.__class__.__name__, smart_repr(self.fields)) diff --git a/authentik/outposts/channels.py b/authentik/outposts/consumer.py similarity index 79% rename from authentik/outposts/channels.py rename to authentik/outposts/consumer.py index f0b656a47..e8c2ee127 100644 --- a/authentik/outposts/channels.py +++ b/authentik/outposts/consumer.py @@ -4,6 +4,7 @@ from datetime import datetime from enum import IntEnum from typing import Any, Optional +from asgiref.sync import async_to_sync from channels.exceptions import DenyConnection from dacite.core import from_dict from dacite.data import Data @@ -14,6 +15,8 @@ from authentik.core.channels import AuthJsonConsumer from authentik.outposts.apps import GAUGE_OUTPOSTS_CONNECTED, GAUGE_OUTPOSTS_LAST_UPDATE from authentik.outposts.models import OUTPOST_HELLO_INTERVAL, Outpost, OutpostState +OUTPOST_GROUP = "group_outpost_%(outpost_pk)s" + class WebsocketMessageInstruction(IntEnum): """Commands which can be triggered over Websocket""" @@ -47,8 +50,6 @@ class OutpostConsumer(AuthJsonConsumer): last_uid: Optional[str] = None - first_msg = False - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.logger = get_logger() @@ -71,22 +72,26 @@ class OutpostConsumer(AuthJsonConsumer): raise DenyConnection() self.outpost = outpost self.last_uid = self.channel_name + async_to_sync(self.channel_layer.group_add)( + OUTPOST_GROUP % {"outpost_pk": str(self.outpost.pk)}, self.channel_name + ) + GAUGE_OUTPOSTS_CONNECTED.labels( + outpost=self.outpost.name, + uid=self.last_uid, + expected=self.outpost.config.kubernetes_replicas, + ).inc() def disconnect(self, code): + if self.outpost: + async_to_sync(self.channel_layer.group_discard)( + OUTPOST_GROUP % {"outpost_pk": str(self.outpost.pk)}, self.channel_name + ) if self.outpost and self.last_uid: - state = OutpostState.for_instance_uid(self.outpost, self.last_uid) - if self.channel_name in state.channel_ids: - state.channel_ids.remove(self.channel_name) - state.save() GAUGE_OUTPOSTS_CONNECTED.labels( outpost=self.outpost.name, uid=self.last_uid, expected=self.outpost.config.kubernetes_replicas, ).dec() - self.logger.debug( - "removed outpost instance from cache", - instance_uuid=self.last_uid, - ) def receive_json(self, content: Data): msg = from_dict(WebsocketMessage, content) @@ -97,26 +102,13 @@ class OutpostConsumer(AuthJsonConsumer): raise DenyConnection() state = OutpostState.for_instance_uid(self.outpost, uid) - if self.channel_name not in state.channel_ids: - state.channel_ids.append(self.channel_name) state.last_seen = datetime.now() - state.hostname = msg.args.get("hostname", "") - - if not self.first_msg: - GAUGE_OUTPOSTS_CONNECTED.labels( - outpost=self.outpost.name, - uid=self.last_uid, - expected=self.outpost.config.kubernetes_replicas, - ).inc() - self.logger.debug( - "added outpost instance to cache", - instance_uuid=self.last_uid, - ) - self.first_msg = True + state.hostname = msg.args.pop("hostname", "") if msg.instruction == WebsocketMessageInstruction.HELLO: - state.version = msg.args.get("version", None) - state.build_hash = msg.args.get("buildHash", "") + state.version = msg.args.pop("version", None) + state.build_hash = msg.args.pop("buildHash", "") + state.args = msg.args elif msg.instruction == WebsocketMessageInstruction.ACK: return GAUGE_OUTPOSTS_LAST_UPDATE.labels( diff --git a/authentik/outposts/migrations/0020_alter_outpost_type.py b/authentik/outposts/migrations/0020_alter_outpost_type.py index 5036137c0..b643f8d47 100644 --- a/authentik/outposts/migrations/0020_alter_outpost_type.py +++ b/authentik/outposts/migrations/0020_alter_outpost_type.py @@ -28,4 +28,8 @@ class Migration(migrations.Migration): verbose_name="Managed by authentik", ), ), + migrations.AlterModelOptions( + name="outpost", + options={"verbose_name": "Outpost", "verbose_name_plural": "Outposts"}, + ), ] diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index 3caae7e73..f876a0cf3 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -405,18 +405,22 @@ class Outpost(SerializerModel, ManagedModel): def __str__(self) -> str: return f"Outpost {self.name}" + class Meta: + verbose_name = _("Outpost") + verbose_name_plural = _("Outposts") + @dataclass class OutpostState: """Outpost instance state, last_seen and version""" uid: str - channel_ids: list[str] = field(default_factory=list) last_seen: Optional[datetime] = field(default=None) version: Optional[str] = field(default=None) version_should: Version = field(default=OUR_VERSION) build_hash: str = field(default="") hostname: str = field(default="") + args: dict = field(default_factory=dict) _outpost: Optional[Outpost] = field(default=None) diff --git a/authentik/outposts/tasks.py b/authentik/outposts/tasks.py index ddb0d5352..b6b3a9bab 100644 --- a/authentik/outposts/tasks.py +++ b/authentik/outposts/tasks.py @@ -25,6 +25,7 @@ from authentik.events.monitored_tasks import ( ) from authentik.lib.config import CONFIG from authentik.lib.utils.reflection import path_to_class +from authentik.outposts.consumer import OUTPOST_GROUP from authentik.outposts.controllers.base import BaseController, ControllerException from authentik.outposts.controllers.docker import DockerClient from authentik.outposts.controllers.kubernetes import KubernetesClient @@ -34,7 +35,6 @@ from authentik.outposts.models import ( Outpost, OutpostModel, OutpostServiceConnection, - OutpostState, OutpostType, ServiceConnectionInvalid, ) @@ -243,10 +243,9 @@ def _outpost_single_update(outpost: Outpost, layer=None): outpost.build_user_permissions(outpost.user) if not layer: # pragma: no cover layer = get_channel_layer() - for state in OutpostState.for_outpost(outpost): - for channel in state.channel_ids: - LOGGER.debug("sending update", channel=channel, instance=state.uid, outpost=outpost) - async_to_sync(layer.send)(channel, {"type": "event.update"}) + group = OUTPOST_GROUP % {"outpost_pk": str(outpost.pk)} + LOGGER.debug("sending update", channel=group, outpost=outpost) + async_to_sync(layer.group_send)(group, {"type": "event.update"}) @CELERY_APP.task( diff --git a/authentik/outposts/tests/test_ws.py b/authentik/outposts/tests/test_ws.py index 9d8546044..b8fcba925 100644 --- a/authentik/outposts/tests/test_ws.py +++ b/authentik/outposts/tests/test_ws.py @@ -7,7 +7,7 @@ from django.test import TransactionTestCase from authentik import __version__ from authentik.core.tests.utils import create_test_flow -from authentik.outposts.channels import WebsocketMessage, WebsocketMessageInstruction +from authentik.outposts.consumer import WebsocketMessage, WebsocketMessageInstruction from authentik.outposts.models import Outpost, OutpostType from authentik.providers.proxy.models import ProxyProvider from authentik.root import websocket diff --git a/authentik/outposts/urls.py b/authentik/outposts/urls.py index 353dfd13c..cd7ba3bf8 100644 --- a/authentik/outposts/urls.py +++ b/authentik/outposts/urls.py @@ -7,7 +7,7 @@ from authentik.outposts.api.service_connections import ( KubernetesServiceConnectionViewSet, ServiceConnectionViewSet, ) -from authentik.outposts.channels import OutpostConsumer +from authentik.outposts.consumer import OutpostConsumer from authentik.root.middleware import ChannelsLoggingMiddleware websocket_urlpatterns = [ diff --git a/authentik/policies/models.py b/authentik/policies/models.py index 2e7d69c61..b99aeb549 100644 --- a/authentik/policies/models.py +++ b/authentik/policies/models.py @@ -190,8 +190,8 @@ class Policy(SerializerModel, CreatedUpdatedModel): verbose_name_plural = _("Policies") permissions = [ - ("view_policy_cache", "View Policy's cache metrics"), - ("clear_policy_cache", "Clear Policy's cache metrics"), + ("view_policy_cache", _("View Policy's cache metrics")), + ("clear_policy_cache", _("Clear Policy's cache metrics")), ] class PolicyMeta: diff --git a/authentik/providers/proxy/tasks.py b/authentik/providers/proxy/tasks.py index 630b0d186..aec8e669a 100644 --- a/authentik/providers/proxy/tasks.py +++ b/authentik/providers/proxy/tasks.py @@ -3,7 +3,8 @@ from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from django.db import DatabaseError, InternalError, ProgrammingError -from authentik.outposts.models import Outpost, OutpostState, OutpostType +from authentik.outposts.consumer import OUTPOST_GROUP +from authentik.outposts.models import Outpost, OutpostType from authentik.providers.proxy.models import ProxyProvider from authentik.root.celery import CELERY_APP @@ -23,13 +24,12 @@ def proxy_on_logout(session_id: str): """Update outpost instances connected to a single outpost""" layer = get_channel_layer() for outpost in Outpost.objects.filter(type=OutpostType.PROXY): - for state in OutpostState.for_outpost(outpost): - for channel in state.channel_ids: - async_to_sync(layer.send)( - channel, - { - "type": "event.provider.specific", - "sub_type": "logout", - "session_id": session_id, - }, - ) + group = OUTPOST_GROUP % {"outpost_pk": str(outpost.pk)} + async_to_sync(layer.group_send)( + group, + { + "type": "event.provider.specific", + "sub_type": "logout", + "session_id": session_id, + }, + ) diff --git a/authentik/providers/radius/api.py b/authentik/providers/radius/api.py index d9a2578ce..af43424bf 100644 --- a/authentik/providers/radius/api.py +++ b/authentik/providers/radius/api.py @@ -21,6 +21,7 @@ class RadiusProviderSerializer(ProviderSerializer): # an admin might have to view it "shared_secret", "outpost_set", + "mfa_support", ] extra_kwargs = ProviderSerializer.Meta.extra_kwargs @@ -55,6 +56,7 @@ class RadiusOutpostConfigSerializer(ModelSerializer): "auth_flow_slug", "client_networks", "shared_secret", + "mfa_support", ] diff --git a/authentik/providers/radius/migrations/0002_radiusprovider_mfa_support.py b/authentik/providers/radius/migrations/0002_radiusprovider_mfa_support.py new file mode 100644 index 000000000..4a8af9e30 --- /dev/null +++ b/authentik/providers/radius/migrations/0002_radiusprovider_mfa_support.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.6 on 2023-10-18 15:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_providers_radius", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="radiusprovider", + name="mfa_support", + field=models.BooleanField( + default=True, + help_text="When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.", + verbose_name="MFA Support", + ), + ), + ] diff --git a/authentik/providers/radius/models.py b/authentik/providers/radius/models.py index 7ba39b47e..781023fd6 100644 --- a/authentik/providers/radius/models.py +++ b/authentik/providers/radius/models.py @@ -27,6 +27,17 @@ class RadiusProvider(OutpostModel, Provider): ), ) + mfa_support = models.BooleanField( + default=True, + verbose_name="MFA Support", + help_text=_( + "When enabled, code-based multi-factor authentication can be used by appending a " + "semicolon and the TOTP code to the password. This should only be enabled if all " + "users that will bind to this provider have a TOTP device configured, as otherwise " + "a password may incorrectly be rejected if it contains a semicolon." + ), + ) + @property def launch_url(self) -> Optional[str]: """Radius never has a launch URL""" diff --git a/authentik/rbac/__init__.py b/authentik/rbac/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/rbac/api/__init__.py b/authentik/rbac/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/rbac/api/rbac.py b/authentik/rbac/api/rbac.py new file mode 100644 index 000000000..3f468f483 --- /dev/null +++ b/authentik/rbac/api/rbac.py @@ -0,0 +1,130 @@ +"""common RBAC serializers""" +from django.apps import apps +from django.contrib.auth.models import Permission +from django.db.models import QuerySet +from django_filters.filters import ModelChoiceFilter +from django_filters.filterset import FilterSet +from rest_framework.exceptions import ValidationError +from rest_framework.fields import ( + CharField, + ChoiceField, + ListField, + ReadOnlyField, + SerializerMethodField, +) +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import ReadOnlyModelViewSet + +from authentik.core.api.utils import PassiveSerializer +from authentik.core.models import User +from authentik.lib.validators import RequiredTogetherValidator +from authentik.policies.event_matcher.models import model_choices +from authentik.rbac.models import Role + + +class PermissionSerializer(ModelSerializer): + """Global permission""" + + app_label = ReadOnlyField(source="content_type.app_label") + app_label_verbose = SerializerMethodField() + model = ReadOnlyField(source="content_type.model") + model_verbose = SerializerMethodField() + + def get_app_label_verbose(self, instance: Permission) -> str: + """Human-readable app label""" + return apps.get_app_config(instance.content_type.app_label).verbose_name + + def get_model_verbose(self, instance: Permission) -> str: + """Human-readable model name""" + return apps.get_model( + instance.content_type.app_label, instance.content_type.model + )._meta.verbose_name + + class Meta: + model = Permission + fields = [ + "id", + "name", + "codename", + "model", + "app_label", + "app_label_verbose", + "model_verbose", + ] + + +class PermissionFilter(FilterSet): + """Filter permissions""" + + role = ModelChoiceFilter(queryset=Role.objects.all(), method="filter_role") + user = ModelChoiceFilter(queryset=User.objects.all()) + + def filter_role(self, queryset: QuerySet, name, value: Role) -> QuerySet: + """Filter permissions based on role""" + return queryset.filter(group__role=value) + + class Meta: + model = Permission + fields = [ + "codename", + "content_type__model", + "content_type__app_label", + "role", + "user", + ] + + +class RBACPermissionViewSet(ReadOnlyModelViewSet): + """Read-only list of all permissions, filterable by model and app""" + + queryset = Permission.objects.none() + serializer_class = PermissionSerializer + ordering = ["name"] + filterset_class = PermissionFilter + search_fields = [ + "codename", + "content_type__model", + "content_type__app_label", + ] + + def get_queryset(self) -> QuerySet: + return ( + Permission.objects.all() + .select_related("content_type") + .filter( + content_type__app_label__startswith="authentik", + ) + ) + + +class PermissionAssignSerializer(PassiveSerializer): + """Request to assign a new permission""" + + permissions = ListField(child=CharField()) + model = ChoiceField(choices=model_choices(), required=False) + object_pk = CharField(required=False) + + validators = [RequiredTogetherValidator(fields=["model", "object_pk"])] + + def validate(self, attrs: dict) -> dict: + model_instance = None + # Check if we're setting an object-level perm or global + model = attrs.get("model") + object_pk = attrs.get("object_pk") + if model and object_pk: + model = apps.get_model(attrs["model"]) + model_instance = model.objects.filter(pk=attrs["object_pk"]).first() + attrs["model_instance"] = model_instance + if attrs.get("model"): + return attrs + permissions = attrs.get("permissions", []) + if not all("." in perm for perm in permissions): + raise ValidationError( + { + "permissions": ( + "When assigning global permissions, codename must be given as " + "app_label.codename" + ) + } + ) + return attrs diff --git a/authentik/rbac/api/rbac_assigned_by_roles.py b/authentik/rbac/api/rbac_assigned_by_roles.py new file mode 100644 index 000000000..5dcdcab12 --- /dev/null +++ b/authentik/rbac/api/rbac_assigned_by_roles.py @@ -0,0 +1,123 @@ +"""common RBAC serializers""" +from django.db.models import Q, QuerySet +from django.db.transaction import atomic +from django_filters.filters import CharFilter, ChoiceFilter +from django_filters.filterset import FilterSet +from drf_spectacular.utils import OpenApiResponse, extend_schema +from guardian.models import GroupObjectPermission +from guardian.shortcuts import assign_perm, remove_perm +from rest_framework.decorators import action +from rest_framework.fields import CharField, ReadOnlyField +from rest_framework.mixins import ListModelMixin +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import GenericViewSet + +from authentik.api.decorators import permission_required +from authentik.core.api.utils import PassiveSerializer +from authentik.policies.event_matcher.models import model_choices +from authentik.rbac.api.rbac import PermissionAssignSerializer +from authentik.rbac.models import Role + + +class RoleObjectPermissionSerializer(ModelSerializer): + """Role-bound object level permission""" + + app_label = ReadOnlyField(source="content_type.app_label") + model = ReadOnlyField(source="content_type.model") + codename = ReadOnlyField(source="permission.codename") + name = ReadOnlyField(source="permission.name") + object_pk = ReadOnlyField() + + class Meta: + model = GroupObjectPermission + fields = ["id", "codename", "model", "app_label", "object_pk", "name"] + + +class RoleAssignedObjectPermissionSerializer(PassiveSerializer): + """Roles assigned object permission serializer""" + + role_pk = CharField(source="group.role.pk", read_only=True) + name = CharField(source="group.name", read_only=True) + permissions = RoleObjectPermissionSerializer( + many=True, source="group.groupobjectpermission_set" + ) + + class Meta: + model = Role + fields = ["role_pk", "name", "permissions"] + + +class RoleAssignedPermissionFilter(FilterSet): + """Role Assigned permission filter""" + + model = ChoiceFilter(choices=model_choices(), method="filter_model", required=True) + object_pk = CharFilter(method="filter_object_pk") + + def filter_model(self, queryset: QuerySet, name, value: str) -> QuerySet: + """Filter by object type""" + app, _, model = value.partition(".") + return queryset.filter( + Q( + group__permissions__content_type__app_label=app, + group__permissions__content_type__model=model, + ) + | Q( + group__groupobjectpermission__permission__content_type__app_label=app, + group__groupobjectpermission__permission__content_type__model=model, + ) + ).distinct() + + def filter_object_pk(self, queryset: QuerySet, name, value: str) -> QuerySet: + """Filter by object primary key""" + return queryset.filter(Q(group__groupobjectpermission__object_pk=value)).distinct() + + +class RoleAssignedPermissionViewSet(ListModelMixin, GenericViewSet): + """Get assigned object permissions for a single object""" + + serializer_class = RoleAssignedObjectPermissionSerializer + ordering = ["name"] + # The filtering is done in the filterset, + # which has a required filter that does the heavy lifting + queryset = Role.objects.all() + filterset_class = RoleAssignedPermissionFilter + + @permission_required("authentik_rbac.assign_role_permissions") + @extend_schema( + request=PermissionAssignSerializer(), + responses={ + 204: OpenApiResponse(description="Successfully assigned"), + }, + ) + @action(methods=["POST"], detail=True, pagination_class=None, filter_backends=[]) + def assign(self, request: Request, *args, **kwargs) -> Response: + """Assign permission(s) to role. When `object_pk` is set, the permissions + are only assigned to the specific object, otherwise they are assigned globally.""" + role: Role = self.get_object() + data = PermissionAssignSerializer(data=request.data) + data.is_valid(raise_exception=True) + with atomic(): + for perm in data.validated_data["permissions"]: + assign_perm(perm, role.group, data.validated_data["model_instance"]) + return Response(status=204) + + @permission_required("authentik_rbac.unassign_role_permissions") + @extend_schema( + request=PermissionAssignSerializer(), + responses={ + 204: OpenApiResponse(description="Successfully unassigned"), + }, + ) + @action(methods=["PATCH"], detail=True, pagination_class=None, filter_backends=[]) + def unassign(self, request: Request, *args, **kwargs) -> Response: + """Unassign permission(s) to role. When `object_pk` is set, the permissions + are only assigned to the specific object, otherwise they are assigned globally.""" + role: Role = self.get_object() + data = PermissionAssignSerializer(data=request.data) + data.is_valid(raise_exception=True) + with atomic(): + for perm in data.validated_data["permissions"]: + remove_perm(perm, role.group, data.validated_data["model_instance"]) + return Response(status=204) diff --git a/authentik/rbac/api/rbac_assigned_by_users.py b/authentik/rbac/api/rbac_assigned_by_users.py new file mode 100644 index 000000000..d69b30a52 --- /dev/null +++ b/authentik/rbac/api/rbac_assigned_by_users.py @@ -0,0 +1,129 @@ +"""common RBAC serializers""" +from django.db.models import Q, QuerySet +from django.db.transaction import atomic +from django_filters.filters import CharFilter, ChoiceFilter +from django_filters.filterset import FilterSet +from drf_spectacular.utils import OpenApiResponse, extend_schema +from guardian.models import UserObjectPermission +from guardian.shortcuts import assign_perm, remove_perm +from rest_framework.decorators import action +from rest_framework.exceptions import ValidationError +from rest_framework.fields import BooleanField, ReadOnlyField +from rest_framework.mixins import ListModelMixin +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import GenericViewSet + +from authentik.api.decorators import permission_required +from authentik.core.api.groups import GroupMemberSerializer +from authentik.core.models import User, UserTypes +from authentik.policies.event_matcher.models import model_choices +from authentik.rbac.api.rbac import PermissionAssignSerializer + + +class UserObjectPermissionSerializer(ModelSerializer): + """User-bound object level permission""" + + app_label = ReadOnlyField(source="content_type.app_label") + model = ReadOnlyField(source="content_type.model") + codename = ReadOnlyField(source="permission.codename") + name = ReadOnlyField(source="permission.name") + object_pk = ReadOnlyField() + + class Meta: + model = UserObjectPermission + fields = ["id", "codename", "model", "app_label", "object_pk", "name"] + + +class UserAssignedObjectPermissionSerializer(GroupMemberSerializer): + """Users assigned object permission serializer""" + + permissions = UserObjectPermissionSerializer(many=True, source="userobjectpermission_set") + is_superuser = BooleanField() + + class Meta: + model = GroupMemberSerializer.Meta.model + fields = GroupMemberSerializer.Meta.fields + ["permissions", "is_superuser"] + + +class UserAssignedPermissionFilter(FilterSet): + """Assigned permission filter""" + + model = ChoiceFilter(choices=model_choices(), method="filter_model", required=True) + object_pk = CharFilter(method="filter_object_pk") + + def filter_model(self, queryset: QuerySet, name, value: str) -> QuerySet: + """Filter by object type""" + app, _, model = value.partition(".") + return queryset.filter( + Q( + user_permissions__content_type__app_label=app, + user_permissions__content_type__model=model, + ) + | Q( + userobjectpermission__permission__content_type__app_label=app, + userobjectpermission__permission__content_type__model=model, + ) + | Q(ak_groups__is_superuser=True) + ).distinct() + + def filter_object_pk(self, queryset: QuerySet, name, value: str) -> QuerySet: + """Filter by object primary key""" + return queryset.filter( + Q(userobjectpermission__object_pk=value) | Q(ak_groups__is_superuser=True), + ).distinct() + + +class UserAssignedPermissionViewSet(ListModelMixin, GenericViewSet): + """Get assigned object permissions for a single object""" + + serializer_class = UserAssignedObjectPermissionSerializer + ordering = ["username"] + # The filtering is done in the filterset, + # which has a required filter that does the heavy lifting + queryset = User.objects.all() + filterset_class = UserAssignedPermissionFilter + + @permission_required("authentik_core.assign_user_permissions") + @extend_schema( + request=PermissionAssignSerializer(), + responses={ + 204: OpenApiResponse(description="Successfully assigned"), + }, + ) + @action(methods=["POST"], detail=True, pagination_class=None, filter_backends=[]) + def assign(self, request: Request, *args, **kwargs) -> Response: + """Assign permission(s) to user""" + user: User = self.get_object() + if user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT: + raise ValidationError("Permissions cannot be assigned to an internal service account.") + data = PermissionAssignSerializer(data=request.data) + data.is_valid(raise_exception=True) + with atomic(): + for perm in data.validated_data["permissions"]: + assign_perm(perm, user, data.validated_data["model_instance"]) + return Response(status=204) + + @permission_required("authentik_core.unassign_user_permissions") + @extend_schema( + request=PermissionAssignSerializer(), + responses={ + 204: OpenApiResponse(description="Successfully unassigned"), + }, + ) + @action(methods=["PATCH"], detail=True, pagination_class=None, filter_backends=[]) + def unassign(self, request: Request, *args, **kwargs) -> Response: + """Unassign permission(s) to user. When `object_pk` is set, the permissions + are only assigned to the specific object, otherwise they are assigned globally.""" + user: User = self.get_object() + if user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT: + raise ValidationError( + "Permissions cannot be unassigned from an internal service account." + ) + data = PermissionAssignSerializer(data=request.data) + data.is_valid(raise_exception=True) + with atomic(): + for perm in data.validated_data["permissions"]: + remove_perm(perm, user, data.validated_data["model_instance"]) + return Response(status=204) diff --git a/authentik/rbac/api/rbac_roles.py b/authentik/rbac/api/rbac_roles.py new file mode 100644 index 000000000..162a3225b --- /dev/null +++ b/authentik/rbac/api/rbac_roles.py @@ -0,0 +1,71 @@ +"""common RBAC serializers""" +from typing import Optional + +from django.apps import apps +from django_filters.filters import UUIDFilter +from django_filters.filterset import FilterSet +from guardian.models import GroupObjectPermission +from guardian.shortcuts import get_objects_for_group +from rest_framework.fields import SerializerMethodField +from rest_framework.mixins import ListModelMixin +from rest_framework.viewsets import GenericViewSet + +from authentik.api.pagination import SmallerPagination +from authentik.rbac.api.rbac_assigned_by_roles import RoleObjectPermissionSerializer + + +class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer): + """User permission with additional object-related data""" + + app_label_verbose = SerializerMethodField() + model_verbose = SerializerMethodField() + + object_description = SerializerMethodField() + + def get_app_label_verbose(self, instance: GroupObjectPermission) -> str: + """Get app label from permission's model""" + return apps.get_app_config(instance.content_type.app_label).verbose_name + + def get_model_verbose(self, instance: GroupObjectPermission) -> str: + """Get model label from permission's model""" + return apps.get_model( + instance.content_type.app_label, instance.content_type.model + )._meta.verbose_name + + def get_object_description(self, instance: GroupObjectPermission) -> Optional[str]: + """Get model description from attached model. This operation takes at least + one additional query, and the description is only shown if the user/role has the + view_ permission on the object""" + app_label = instance.content_type.app_label + model = instance.content_type.model + model_class = apps.get_model(app_label, model) + objects = get_objects_for_group(instance.group, f"{app_label}.view_{model}", model_class) + obj = objects.first() + if not obj: + return None + return str(obj) + + class Meta(RoleObjectPermissionSerializer.Meta): + fields = RoleObjectPermissionSerializer.Meta.fields + [ + "app_label_verbose", + "model_verbose", + "object_description", + ] + + +class RolePermissionFilter(FilterSet): + """Role permission filter""" + + uuid = UUIDFilter("group__role__uuid", required=True) + + +class RolePermissionViewSet(ListModelMixin, GenericViewSet): + """Get a role's assigned object permissions""" + + serializer_class = ExtraRoleObjectPermissionSerializer + ordering = ["group__role__name"] + pagination_class = SmallerPagination + # The filtering is done in the filterset, + # which has a required filter that does the heavy lifting + queryset = GroupObjectPermission.objects.select_related("content_type", "group__role").all() + filterset_class = RolePermissionFilter diff --git a/authentik/rbac/api/rbac_users.py b/authentik/rbac/api/rbac_users.py new file mode 100644 index 000000000..04f3fcabd --- /dev/null +++ b/authentik/rbac/api/rbac_users.py @@ -0,0 +1,71 @@ +"""common RBAC serializers""" +from typing import Optional + +from django.apps import apps +from django_filters.filters import NumberFilter +from django_filters.filterset import FilterSet +from guardian.models import UserObjectPermission +from guardian.shortcuts import get_objects_for_user +from rest_framework.fields import SerializerMethodField +from rest_framework.mixins import ListModelMixin +from rest_framework.viewsets import GenericViewSet + +from authentik.api.pagination import SmallerPagination +from authentik.rbac.api.rbac_assigned_by_users import UserObjectPermissionSerializer + + +class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer): + """User permission with additional object-related data""" + + app_label_verbose = SerializerMethodField() + model_verbose = SerializerMethodField() + + object_description = SerializerMethodField() + + def get_app_label_verbose(self, instance: UserObjectPermission) -> str: + """Get app label from permission's model""" + return apps.get_app_config(instance.content_type.app_label).verbose_name + + def get_model_verbose(self, instance: UserObjectPermission) -> str: + """Get model label from permission's model""" + return apps.get_model( + instance.content_type.app_label, instance.content_type.model + )._meta.verbose_name + + def get_object_description(self, instance: UserObjectPermission) -> Optional[str]: + """Get model description from attached model. This operation takes at least + one additional query, and the description is only shown if the user/role has the + view_ permission on the object""" + app_label = instance.content_type.app_label + model = instance.content_type.model + model_class = apps.get_model(app_label, model) + objects = get_objects_for_user(instance.user, f"{app_label}.view_{model}", model_class) + obj = objects.first() + if not obj: + return None + return str(obj) + + class Meta(UserObjectPermissionSerializer.Meta): + fields = UserObjectPermissionSerializer.Meta.fields + [ + "app_label_verbose", + "model_verbose", + "object_description", + ] + + +class UserPermissionFilter(FilterSet): + """User-assigned permission filter""" + + user_id = NumberFilter("user__id", required=True) + + +class UserPermissionViewSet(ListModelMixin, GenericViewSet): + """Get a users's assigned object permissions""" + + serializer_class = ExtraUserObjectPermissionSerializer + ordering = ["user__username"] + pagination_class = SmallerPagination + # The filtering is done in the filterset, + # which has a required filter that does the heavy lifting + queryset = UserObjectPermission.objects.select_related("content_type", "user").all() + filterset_class = UserPermissionFilter diff --git a/authentik/rbac/api/roles.py b/authentik/rbac/api/roles.py new file mode 100644 index 000000000..36eef8a19 --- /dev/null +++ b/authentik/rbac/api/roles.py @@ -0,0 +1,24 @@ +"""RBAC Roles""" +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import ModelViewSet + +from authentik.core.api.used_by import UsedByMixin +from authentik.rbac.models import Role + + +class RoleSerializer(ModelSerializer): + """Role serializer""" + + class Meta: + model = Role + fields = ["pk", "name"] + + +class RoleViewSet(UsedByMixin, ModelViewSet): + """Role viewset""" + + serializer_class = RoleSerializer + queryset = Role.objects.all() + search_fields = ["group__name"] + ordering = ["group__name"] + filterset_fields = ["group__name"] diff --git a/authentik/rbac/apps.py b/authentik/rbac/apps.py new file mode 100644 index 000000000..f6b878c01 --- /dev/null +++ b/authentik/rbac/apps.py @@ -0,0 +1,15 @@ +"""authentik rbac app config""" +from authentik.blueprints.apps import ManagedAppConfig + + +class AuthentikRBACConfig(ManagedAppConfig): + """authentik rbac app config""" + + name = "authentik.rbac" + label = "authentik_rbac" + verbose_name = "authentik RBAC" + default = True + + def reconcile_load_rbac_signals(self): + """Load rbac signals""" + self.import_module("authentik.rbac.signals") diff --git a/authentik/rbac/filters.py b/authentik/rbac/filters.py new file mode 100644 index 000000000..22f5a768e --- /dev/null +++ b/authentik/rbac/filters.py @@ -0,0 +1,33 @@ +"""RBAC API Filter""" +from django.db.models import QuerySet +from rest_framework.exceptions import PermissionDenied +from rest_framework.request import Request +from rest_framework_guardian.filters import ObjectPermissionsFilter + +from authentik.core.models import UserTypes + + +class ObjectFilter(ObjectPermissionsFilter): + """Object permission filter that grants global permission higher priority than + per-object permissions""" + + def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet: + permission = self.perm_format % { + "app_label": queryset.model._meta.app_label, + "model_name": queryset.model._meta.model_name, + } + # having the global permission set on a user has higher priority than + # per-object permissions + if request.user.has_perm(permission): + return queryset + queryset = super().filter_queryset(request, queryset, view) + # Outposts (which are the only objects using internal service accounts) + # except requests to return an empty list when they have no objects + # assigned + if request.user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT: + return queryset + if not queryset.exists(): + # User doesn't have direct permission to all objects + # and also no object permissions assigned (directly or via role) + raise PermissionDenied() + return queryset diff --git a/authentik/rbac/migrations/0001_initial.py b/authentik/rbac/migrations/0001_initial.py new file mode 100644 index 000000000..85cabbf11 --- /dev/null +++ b/authentik/rbac/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.6 on 2023-10-11 13:37 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="Role", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("name", models.TextField(max_length=150, unique=True)), + ( + "group", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, to="auth.group" + ), + ), + ], + options={ + "verbose_name": "Role", + "verbose_name_plural": "Roles", + "permissions": [ + ("assign_role_permissions", "Can assign permissions to users"), + ("unassign_role_permissions", "Can unassign permissions from users"), + ], + }, + ), + ] diff --git a/authentik/rbac/migrations/0002_systempermission.py b/authentik/rbac/migrations/0002_systempermission.py new file mode 100644 index 000000000..8a08c09fb --- /dev/null +++ b/authentik/rbac/migrations/0002_systempermission.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.6 on 2023-10-12 15:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_rbac", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="SystemPermission", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ], + options={ + "permissions": [ + ("view_system_info", "Can view system info"), + ("view_system_tasks", "Can view system tasks"), + ("run_system_tasks", "Can run system tasks"), + ("access_admin_interface", "Can access admin interface"), + ], + "verbose_name": "System permission", + "verbose_name_plural": "System permissions", + "managed": False, + "default_permissions": (), + }, + ), + ] diff --git a/authentik/rbac/migrations/__init__.py b/authentik/rbac/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/rbac/models.py b/authentik/rbac/models.py new file mode 100644 index 000000000..fe6096f7d --- /dev/null +++ b/authentik/rbac/models.py @@ -0,0 +1,73 @@ +"""RBAC models""" +from typing import Optional +from uuid import uuid4 + +from django.db import models +from django.db.transaction import atomic +from django.utils.translation import gettext_lazy as _ +from guardian.shortcuts import assign_perm +from rest_framework.serializers import BaseSerializer + +from authentik.lib.models import SerializerModel + + +class Role(SerializerModel): + """RBAC role, which can have different permissions (both global and per-object) attached + to it.""" + + uuid = models.UUIDField(default=uuid4, editable=False, unique=True, primary_key=True) + # Due to the way django and django-guardian work, this is somewhat of a hack. + # Django and django-guardian allow for setting permissions on users and groups, but they + # only allow for a custom user object, not a custom group object, which is why + # we have both authentik and django groups. With this model, we use the inbuilt group system + # for RBAC. This means that every Role needs a single django group that its assigned to + # which will hold all of the actual permissions + # The main advantage of that is that all the permission checking just works out of the box, + # as these permissions are checked by default by django and most other libraries that build + # on top of django + group = models.OneToOneField("auth.Group", on_delete=models.CASCADE) + + # name field has the same constraints as the group model + name = models.TextField(max_length=150, unique=True) + + def assign_permission(self, *perms: str, obj: Optional[models.Model] = None): + """Assign permission to role, can handle multiple permissions, + but when assigning multiple permissions to an object the permissions + must all belong to the object given""" + with atomic(): + for perm in perms: + assign_perm(perm, self.group, obj) + + @property + def serializer(self) -> type[BaseSerializer]: + from authentik.rbac.api.roles import RoleSerializer + + return RoleSerializer + + def __str__(self) -> str: + return f"Role {self.name}" + + class Meta: + verbose_name = _("Role") + verbose_name_plural = _("Roles") + permissions = [ + ("assign_role_permissions", _("Can assign permissions to users")), + ("unassign_role_permissions", _("Can unassign permissions from users")), + ] + + +class SystemPermission(models.Model): + """System-wide permissions that are not related to any direct + database model""" + + class Meta: + managed = False + default_permissions = () + verbose_name = _("System permission") + verbose_name_plural = _("System permissions") + permissions = [ + ("view_system_info", _("Can view system info")), + ("view_system_tasks", _("Can view system tasks")), + ("run_system_tasks", _("Can run system tasks")), + ("access_admin_interface", _("Can access admin interface")), + ] diff --git a/authentik/rbac/permissions.py b/authentik/rbac/permissions.py new file mode 100644 index 000000000..011a02715 --- /dev/null +++ b/authentik/rbac/permissions.py @@ -0,0 +1,30 @@ +"""RBAC Permissions""" +from django.db.models import Model +from rest_framework.permissions import BasePermission, DjangoObjectPermissions +from rest_framework.request import Request + + +class ObjectPermissions(DjangoObjectPermissions): + """RBAC Permissions""" + + def has_object_permission(self, request: Request, view, obj: Model): + queryset = self._queryset(view) + model_cls = queryset.model + perms = self.get_required_object_permissions(request.method, model_cls) + # Rank global permissions higher than per-object permissions + if request.user.has_perms(perms): + return True + return super().has_object_permission(request, view, obj) + + +# pylint: disable=invalid-name +def HasPermission(*perm: str) -> type[BasePermission]: + """Permission checker for any non-object permissions, returns + a BasePermission class that can be used with rest_framework""" + + # pylint: disable=missing-class-docstring, invalid-name + class checker(BasePermission): + def has_permission(self, request: Request, view): + return bool(request.user and request.user.has_perms(perm)) + + return checker diff --git a/authentik/rbac/signals.py b/authentik/rbac/signals.py new file mode 100644 index 000000000..f3bbbc036 --- /dev/null +++ b/authentik/rbac/signals.py @@ -0,0 +1,67 @@ +"""rbac signals""" +from django.contrib.auth.models import Group as DjangoGroup +from django.db.models.signals import m2m_changed, pre_save +from django.db.transaction import atomic +from django.dispatch import receiver +from structlog.stdlib import get_logger + +from authentik.core.models import Group +from authentik.rbac.models import Role + +LOGGER = get_logger() + + +@receiver(pre_save, sender=Role) +def rbac_role_pre_save(sender: type[Role], instance: Role, **_): + """Ensure role has a group object created for it""" + if hasattr(instance, "group"): + return + group, _ = DjangoGroup.objects.get_or_create(name=instance.name) + instance.group = group + + +@receiver(m2m_changed, sender=Group.roles.through) +def rbac_group_role_m2m(sender: type[Group], action: str, instance: Group, reverse: bool, **_): + """RBAC: Sync group members into roles when roles are assigned""" + if action not in ["post_add", "post_remove", "post_clear"]: + return + with atomic(): + group_users = list( + instance.children_recursive() + .exclude(users__isnull=True) + .values_list("users", flat=True) + ) + if not group_users: + return + for role in instance.roles.all(): + role: Role + role.group.user_set.set(group_users) + LOGGER.debug("Updated users in group", group=instance) + + +# pylint: disable=no-member +@receiver(m2m_changed, sender=Group.users.through) +def rbac_group_users_m2m( + sender: type[Group], action: str, instance: Group, pk_set: set, reverse: bool, **_ +): + """Handle Group/User m2m and mirror it to roles""" + if action not in ["post_add", "post_remove"]: + return + # reverse: instance is a Group, pk_set is a list of user pks + # non-reverse: instance is a User, pk_set is a list of groups + with atomic(): + if reverse: + for role in instance.roles.all(): + role: Role + if action == "post_add": + role.group.user_set.add(*pk_set) + elif action == "post_remove": + role.group.user_set.remove(*pk_set) + else: + for group in Group.objects.filter(pk__in=pk_set): + for role in group.roles.all(): + role: Role + if action == "post_add": + role.group.user_set.add(instance) + elif action == "post_remove": + role.group.user_set.remove(instance) diff --git a/authentik/rbac/tests/__init__.py b/authentik/rbac/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/rbac/tests/test_api_assigned_by_roles.py b/authentik/rbac/tests/test_api_assigned_by_roles.py new file mode 100644 index 000000000..07032e805 --- /dev/null +++ b/authentik/rbac/tests/test_api_assigned_by_roles.py @@ -0,0 +1,151 @@ +"""Test RoleAssignedPermissionViewSet api""" +from django.urls import reverse +from rest_framework.test import APITestCase + +from authentik.core.models import Group +from authentik.core.tests.utils import create_test_admin_user, create_test_user +from authentik.lib.generators import generate_id +from authentik.rbac.api.rbac_assigned_by_roles import RoleAssignedObjectPermissionSerializer +from authentik.rbac.models import Role +from authentik.stages.invitation.models import Invitation + + +class TestRBACRoleAPI(APITestCase): + """Test RoleAssignedPermissionViewSet api""" + + def setUp(self) -> None: + self.superuser = create_test_admin_user() + + self.user = create_test_user() + self.role = Role.objects.create(name=generate_id()) + self.group = Group.objects.create(name=generate_id()) + self.group.roles.add(self.role) + self.group.users.add(self.user) + + def test_filter_assigned(self): + """Test RoleAssignedPermissionViewSet's filters""" + inv = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + self.role.assign_permission("authentik_stages_invitation.view_invitation", obj=inv) + # self.user doesn't have permissions to see their (object) permissions + self.client.force_login(self.superuser) + res = self.client.get( + reverse("authentik_api:permissions-assigned-by-roles-list"), + { + "model": "authentik_stages_invitation.invitation", + "object_pk": str(inv.pk), + "ordering": "pk", + }, + ) + self.assertEqual(res.status_code, 200) + self.assertJSONEqual( + res.content.decode(), + { + "pagination": { + "next": 0, + "previous": 0, + "count": 1, + "current": 1, + "total_pages": 1, + "start_index": 1, + "end_index": 1, + }, + "results": [ + RoleAssignedObjectPermissionSerializer(instance=self.role).data, + ], + }, + ) + + def test_assign_global(self): + """Test permission assign""" + self.client.force_login(self.superuser) + res = self.client.post( + reverse( + "authentik_api:permissions-assigned-by-roles-assign", + kwargs={ + "pk": self.role.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + }, + ) + self.assertEqual(res.status_code, 204) + self.assertTrue(self.user.has_perm("authentik_stages_invitation.view_invitation")) + + def test_assign_object(self): + """Test permission assign (object)""" + inv = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + self.client.force_login(self.superuser) + res = self.client.post( + reverse( + "authentik_api:permissions-assigned-by-roles-assign", + kwargs={ + "pk": self.role.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + "model": "authentik_stages_invitation.invitation", + "object_pk": str(inv.pk), + }, + ) + self.assertEqual(res.status_code, 204) + self.assertTrue( + self.user.has_perm( + "authentik_stages_invitation.view_invitation", + inv, + ) + ) + + def test_unassign_global(self): + """Test permission unassign""" + self.role.assign_permission("authentik_stages_invitation.view_invitation") + self.client.force_login(self.superuser) + res = self.client.patch( + reverse( + "authentik_api:permissions-assigned-by-roles-unassign", + kwargs={ + "pk": str(self.role.pk), + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + }, + ) + self.assertEqual(res.status_code, 204) + self.assertFalse(self.user.has_perm("authentik_stages_invitation.view_invitation")) + + def test_unassign_object(self): + """Test permission unassign (object)""" + inv = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + self.role.assign_permission("authentik_stages_invitation.view_invitation", obj=inv) + self.client.force_login(self.superuser) + res = self.client.patch( + reverse( + "authentik_api:permissions-assigned-by-roles-unassign", + kwargs={ + "pk": str(self.role.pk), + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + "model": "authentik_stages_invitation.invitation", + "object_pk": str(inv.pk), + }, + ) + self.assertEqual(res.status_code, 204) + self.assertFalse( + self.user.has_perm( + "authentik_stages_invitation.view_invitation", + inv, + ) + ) diff --git a/authentik/rbac/tests/test_api_assigned_by_users.py b/authentik/rbac/tests/test_api_assigned_by_users.py new file mode 100644 index 000000000..fa1238495 --- /dev/null +++ b/authentik/rbac/tests/test_api_assigned_by_users.py @@ -0,0 +1,196 @@ +"""Test UserAssignedPermissionViewSet api""" +from django.urls import reverse +from guardian.shortcuts import assign_perm +from rest_framework.test import APITestCase + +from authentik.core.models import Group, UserTypes +from authentik.core.tests.utils import create_test_admin_user, create_test_user +from authentik.lib.generators import generate_id +from authentik.rbac.api.rbac_assigned_by_users import UserAssignedObjectPermissionSerializer +from authentik.rbac.models import Role +from authentik.stages.invitation.models import Invitation + + +class TestRBACUserAPI(APITestCase): + """Test UserAssignedPermissionViewSet api""" + + def setUp(self) -> None: + self.superuser = create_test_admin_user() + + self.user = create_test_user() + self.role = Role.objects.create(name=generate_id()) + self.group = Group.objects.create(name=generate_id()) + self.group.roles.add(self.role) + self.group.users.add(self.user) + + def test_filter_assigned(self): + """Test UserAssignedPermissionViewSet's filters""" + inv = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + assign_perm("authentik_stages_invitation.view_invitation", self.user, inv) + # self.user doesn't have permissions to see their (object) permissions + self.client.force_login(self.superuser) + res = self.client.get( + reverse("authentik_api:permissions-assigned-by-users-list"), + { + "model": "authentik_stages_invitation.invitation", + "object_pk": str(inv.pk), + "ordering": "pk", + }, + ) + self.assertEqual(res.status_code, 200) + self.assertJSONEqual( + res.content.decode(), + { + "pagination": { + "next": 0, + "previous": 0, + "count": 2, + "current": 1, + "total_pages": 1, + "start_index": 1, + "end_index": 2, + }, + "results": sorted( + [ + UserAssignedObjectPermissionSerializer(instance=self.user).data, + UserAssignedObjectPermissionSerializer(instance=self.superuser).data, + ], + key=lambda u: u["pk"], + ), + }, + ) + + def test_assign_global(self): + """Test permission assign""" + self.client.force_login(self.superuser) + res = self.client.post( + reverse( + "authentik_api:permissions-assigned-by-users-assign", + kwargs={ + "pk": self.user.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + }, + ) + self.assertEqual(res.status_code, 204) + self.assertTrue(self.user.has_perm("authentik_stages_invitation.view_invitation")) + + def test_assign_global_internal_sa(self): + """Test permission assign (to internal service account)""" + self.client.force_login(self.superuser) + self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT + self.user.save() + res = self.client.post( + reverse( + "authentik_api:permissions-assigned-by-users-assign", + kwargs={ + "pk": self.user.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + }, + ) + self.assertEqual(res.status_code, 400) + self.assertFalse(self.user.has_perm("authentik_stages_invitation.view_invitation")) + + def test_assign_object(self): + """Test permission assign (object)""" + inv = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + self.client.force_login(self.superuser) + res = self.client.post( + reverse( + "authentik_api:permissions-assigned-by-users-assign", + kwargs={ + "pk": self.user.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + "model": "authentik_stages_invitation.invitation", + "object_pk": str(inv.pk), + }, + ) + self.assertEqual(res.status_code, 204) + self.assertTrue( + self.user.has_perm( + "authentik_stages_invitation.view_invitation", + inv, + ) + ) + + def test_unassign_global(self): + """Test permission unassign""" + assign_perm("authentik_stages_invitation.view_invitation", self.user) + self.client.force_login(self.superuser) + res = self.client.patch( + reverse( + "authentik_api:permissions-assigned-by-users-unassign", + kwargs={ + "pk": self.user.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + }, + ) + self.assertEqual(res.status_code, 204) + self.assertFalse(self.user.has_perm("authentik_stages_invitation.view_invitation")) + + def test_unassign_global_internal_sa(self): + """Test permission unassign (from internal service account)""" + self.client.force_login(self.superuser) + self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT + self.user.save() + assign_perm("authentik_stages_invitation.view_invitation", self.user) + self.client.force_login(self.superuser) + res = self.client.patch( + reverse( + "authentik_api:permissions-assigned-by-users-unassign", + kwargs={ + "pk": self.user.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + }, + ) + self.assertEqual(res.status_code, 400) + self.assertTrue(self.user.has_perm("authentik_stages_invitation.view_invitation")) + + def test_unassign_object(self): + """Test permission unassign (object)""" + inv = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + assign_perm("authentik_stages_invitation.view_invitation", self.user, inv) + self.client.force_login(self.superuser) + res = self.client.patch( + reverse( + "authentik_api:permissions-assigned-by-users-unassign", + kwargs={ + "pk": self.user.pk, + }, + ), + { + "permissions": ["authentik_stages_invitation.view_invitation"], + "model": "authentik_stages_invitation.invitation", + "object_pk": str(inv.pk), + }, + ) + self.assertEqual(res.status_code, 204) + self.assertFalse( + self.user.has_perm( + "authentik_stages_invitation.view_invitation", + inv, + ) + ) diff --git a/authentik/rbac/tests/test_api_filters.py b/authentik/rbac/tests/test_api_filters.py new file mode 100644 index 000000000..91bd707d7 --- /dev/null +++ b/authentik/rbac/tests/test_api_filters.py @@ -0,0 +1,122 @@ +"""RBAC role tests""" +from django.urls import reverse +from rest_framework.test import APITestCase + +from authentik.core.models import Group +from authentik.core.tests.utils import create_test_admin_user, create_test_user +from authentik.lib.generators import generate_id +from authentik.rbac.models import Role +from authentik.stages.invitation.api import InvitationSerializer +from authentik.stages.invitation.models import Invitation + + +class TestAPIPerms(APITestCase): + """Test API Permission and filtering""" + + def setUp(self) -> None: + self.superuser = create_test_admin_user() + + self.user = create_test_user() + self.role = Role.objects.create(name=generate_id()) + self.group = Group.objects.create(name=generate_id()) + self.group.roles.add(self.role) + self.group.users.add(self.user) + + def test_list_simple(self): + """Test list (single object, role has global permission)""" + self.client.force_login(self.user) + self.role.assign_permission("authentik_stages_invitation.view_invitation") + + Invitation.objects.all().delete() + inv = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + res = self.client.get(reverse("authentik_api:invitation-list")) + self.assertEqual(res.status_code, 200) + self.assertJSONEqual( + res.content.decode(), + { + "pagination": { + "next": 0, + "previous": 0, + "count": 1, + "current": 1, + "total_pages": 1, + "start_index": 1, + "end_index": 1, + }, + "results": [ + InvitationSerializer(instance=inv).data, + ], + }, + ) + + def test_list_object_perm(self): + """Test list""" + self.client.force_login(self.user) + + Invitation.objects.all().delete() + Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + inv2 = Invitation.objects.create( + name=generate_id(), + created_by=self.superuser, + ) + self.role.assign_permission("authentik_stages_invitation.view_invitation", obj=inv2) + + res = self.client.get(reverse("authentik_api:invitation-list")) + self.assertEqual(res.status_code, 200) + self.assertJSONEqual( + res.content.decode(), + { + "pagination": { + "next": 0, + "previous": 0, + "count": 1, + "current": 1, + "total_pages": 1, + "start_index": 1, + "end_index": 1, + }, + "results": [ + InvitationSerializer(instance=inv2).data, + ], + }, + ) + + def test_list_denied(self): + """Test list without adding permission""" + self.client.force_login(self.user) + + res = self.client.get(reverse("authentik_api:invitation-list")) + self.assertEqual(res.status_code, 403) + self.assertJSONEqual( + res.content.decode(), + {"detail": "You do not have permission to perform this action."}, + ) + + def test_create_simple(self): + """Test create with permission""" + self.client.force_login(self.user) + self.role.assign_permission("authentik_stages_invitation.add_invitation") + res = self.client.post( + reverse("authentik_api:invitation-list"), + data={ + "name": generate_id(), + }, + ) + self.assertEqual(res.status_code, 201) + + def test_create_simple_denied(self): + """Test create without assigning permission""" + self.client.force_login(self.user) + res = self.client.post( + reverse("authentik_api:invitation-list"), + data={ + "name": generate_id(), + }, + ) + self.assertEqual(res.status_code, 403) diff --git a/authentik/rbac/tests/test_roles.py b/authentik/rbac/tests/test_roles.py new file mode 100644 index 000000000..f9cbfdabb --- /dev/null +++ b/authentik/rbac/tests/test_roles.py @@ -0,0 +1,35 @@ +"""RBAC role tests""" +from rest_framework.test import APITestCase + +from authentik.core.models import Group +from authentik.core.tests.utils import create_test_admin_user +from authentik.lib.generators import generate_id +from authentik.rbac.models import Role + + +class TestRoles(APITestCase): + """Test roles""" + + def test_role_create(self): + """Test creation""" + user = create_test_admin_user() + group = Group.objects.create(name=generate_id()) + role = Role.objects.create(name=generate_id()) + role.assign_permission("authentik_core.view_application") + group.roles.add(role) + group.users.add(user) + self.assertEqual(list(role.group.user_set.all()), [user]) + self.assertTrue(user.has_perm("authentik_core.view_application")) + + def test_role_create_remove(self): + """Test creation and remove""" + user = create_test_admin_user() + group = Group.objects.create(name=generate_id()) + role = Role.objects.create(name=generate_id()) + role.assign_permission("authentik_core.view_application") + group.roles.add(role) + group.users.add(user) + self.assertEqual(list(role.group.user_set.all()), [user]) + self.assertTrue(user.has_perm("authentik_core.view_application")) + user.delete() + self.assertEqual(list(role.group.user_set.all()), []) diff --git a/authentik/rbac/urls.py b/authentik/rbac/urls.py new file mode 100644 index 000000000..586264a50 --- /dev/null +++ b/authentik/rbac/urls.py @@ -0,0 +1,24 @@ +"""RBAC API urls""" +from authentik.rbac.api.rbac import RBACPermissionViewSet +from authentik.rbac.api.rbac_assigned_by_roles import RoleAssignedPermissionViewSet +from authentik.rbac.api.rbac_assigned_by_users import UserAssignedPermissionViewSet +from authentik.rbac.api.rbac_roles import RolePermissionViewSet +from authentik.rbac.api.rbac_users import UserPermissionViewSet +from authentik.rbac.api.roles import RoleViewSet + +api_urlpatterns = [ + ( + "rbac/permissions/assigned_by_users", + UserAssignedPermissionViewSet, + "permissions-assigned-by-users", + ), + ( + "rbac/permissions/assigned_by_roles", + RoleAssignedPermissionViewSet, + "permissions-assigned-by-roles", + ), + ("rbac/permissions/users", UserPermissionViewSet, "permissions-users"), + ("rbac/permissions/roles", RolePermissionViewSet, "permissions-roles"), + ("rbac/permissions", RBACPermissionViewSet), + ("rbac/roles", RoleViewSet), +] diff --git a/authentik/root/settings.py b/authentik/root/settings.py index a7ed583ae..ee31f2cc6 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -77,6 +77,7 @@ INSTALLED_APPS = [ "authentik.providers.radius", "authentik.providers.saml", "authentik.providers.scim", + "authentik.rbac", "authentik.recovery", "authentik.sources.ldap", "authentik.sources.oauth", @@ -156,7 +157,7 @@ REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination", "PAGE_SIZE": 100, "DEFAULT_FILTER_BACKENDS": [ - "rest_framework_guardian.filters.ObjectPermissionsFilter", + "authentik.rbac.filters.ObjectFilter", "django_filters.rest_framework.DjangoFilterBackend", "rest_framework.filters.OrderingFilter", "rest_framework.filters.SearchFilter", @@ -164,7 +165,7 @@ REST_FRAMEWORK = { "DEFAULT_PARSER_CLASSES": [ "rest_framework.parsers.JSONParser", ], - "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.DjangoObjectPermissions",), + "DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",), "DEFAULT_AUTHENTICATION_CLASSES": ( "authentik.api.authentication.TokenAuthentication", "rest_framework.authentication.SessionAuthentication", @@ -253,10 +254,10 @@ ASGI_APPLICATION = "authentik.root.asgi.application" CHANNEL_LAYERS = { "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", + "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", "CONFIG": { "hosts": [f"{_redis_url}/{CONFIG.get('redis.db')}"], - "prefix": "authentik_channels", + "prefix": "authentik_channels_", }, }, } @@ -410,6 +411,9 @@ if DEBUG: INSTALLED_APPS.append("silk") SILKY_PYTHON_PROFILER = True MIDDLEWARE = ["silk.middleware.SilkyMiddleware"] + MIDDLEWARE + REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append( + "rest_framework.renderers.BrowsableAPIRenderer" + ) INSTALLED_APPS.append("authentik.core") diff --git a/authentik/sources/ldap/password.py b/authentik/sources/ldap/password.py index 43b09befc..d4e0dff4e 100644 --- a/authentik/sources/ldap/password.py +++ b/authentik/sources/ldap/password.py @@ -49,6 +49,11 @@ class LDAPPasswordChanger: self._source = source self._connection = source.connection() + @staticmethod + def should_check_user(user: User) -> bool: + """Check if the user has LDAP parameters and needs to be checked""" + return LDAP_DISTINGUISHED_NAME in user.attributes + def get_domain_root_dn(self) -> str: """Attempt to get root DN via MS specific fields or generic LDAP fields""" info = self._connection.server.info diff --git a/authentik/sources/ldap/signals.py b/authentik/sources/ldap/signals.py index a5f7ea037..5af97376d 100644 --- a/authentik/sources/ldap/signals.py +++ b/authentik/sources/ldap/signals.py @@ -41,11 +41,12 @@ def ldap_password_validate(sender, password: str, plan_context: dict[str, Any], if not sources.exists(): return source = sources.first() + user = plan_context.get(PLAN_CONTEXT_PENDING_USER, None) + if user and not LDAPPasswordChanger.should_check_user(user): + return changer = LDAPPasswordChanger(source) if changer.check_ad_password_complexity_enabled(): - passing = changer.ad_password_complexity( - password, plan_context.get(PLAN_CONTEXT_PENDING_USER, None) - ) + passing = changer.ad_password_complexity(password, user) if not passing: raise ValidationError(_("Password does not match Active Directory Complexity.")) @@ -57,6 +58,8 @@ def ldap_sync_password(sender, user: User, password: str, **_): if not sources.exists(): return source = sources.first() + if not LDAPPasswordChanger.should_check_user(user): + return try: changer = LDAPPasswordChanger(source) changer.change_password(user, password) diff --git a/authentik/sources/ldap/tasks.py b/authentik/sources/ldap/tasks.py index 026f398b6..9c4d6af73 100644 --- a/authentik/sources/ldap/tasks.py +++ b/authentik/sources/ldap/tasks.py @@ -32,7 +32,7 @@ CACHE_KEY_PREFIX = "goauthentik.io/sources/ldap/page/" def ldap_sync_all(): """Sync all sources""" for source in LDAPSource.objects.filter(enabled=True): - ldap_sync_single(source.pk) + ldap_sync_single.apply_async(args=[source.pk]) @CELERY_APP.task( diff --git a/authentik/stages/authenticator_static/migrations/0009_throttling.py b/authentik/stages/authenticator_static/migrations/0009_throttling.py index 17690de2e..1883f8836 100644 --- a/authentik/stages/authenticator_static/migrations/0009_throttling.py +++ b/authentik/stages/authenticator_static/migrations/0009_throttling.py @@ -30,4 +30,12 @@ class Migration(migrations.Migration): name="staticdevice", options={"verbose_name": "Static device", "verbose_name_plural": "Static devices"}, ), + migrations.AlterModelOptions( + name="staticdevice", + options={"verbose_name": "Static Device", "verbose_name_plural": "Static Devices"}, + ), + migrations.AlterModelOptions( + name="statictoken", + options={"verbose_name": "Static Token", "verbose_name_plural": "Static Tokens"}, + ), ] diff --git a/authentik/stages/authenticator_static/models.py b/authentik/stages/authenticator_static/models.py index ac8b55b08..7ce345159 100644 --- a/authentik/stages/authenticator_static/models.py +++ b/authentik/stages/authenticator_static/models.py @@ -95,8 +95,8 @@ class StaticDevice(SerializerModel, ThrottlingMixin, Device): return match is not None class Meta(Device.Meta): - verbose_name = _("Static device") - verbose_name_plural = _("Static devices") + verbose_name = _("Static Device") + verbose_name_plural = _("Static Devices") class StaticToken(models.Model): @@ -124,3 +124,7 @@ class StaticToken(models.Model): """ return b32encode(urandom(5)).decode("utf-8").lower() + + class Meta: + verbose_name = _("Static Token") + verbose_name_plural = _("Static Tokens") diff --git a/authentik/stages/authenticator_totp/migrations/0010_alter_totpdevice_key.py b/authentik/stages/authenticator_totp/migrations/0010_alter_totpdevice_key.py index 436eaa38a..af007e4df 100644 --- a/authentik/stages/authenticator_totp/migrations/0010_alter_totpdevice_key.py +++ b/authentik/stages/authenticator_totp/migrations/0010_alter_totpdevice_key.py @@ -25,4 +25,8 @@ class Migration(migrations.Migration): name="totpdevice", options={"verbose_name": "TOTP device", "verbose_name_plural": "TOTP devices"}, ), + migrations.AlterModelOptions( + name="totpdevice", + options={"verbose_name": "TOTP Device", "verbose_name_plural": "TOTP Devices"}, + ), ] diff --git a/authentik/stages/authenticator_totp/models.py b/authentik/stages/authenticator_totp/models.py index 6828a8e2e..41bf2d2c8 100644 --- a/authentik/stages/authenticator_totp/models.py +++ b/authentik/stages/authenticator_totp/models.py @@ -241,5 +241,5 @@ class TOTPDevice(SerializerModel, ThrottlingMixin, Device): return None class Meta(Device.Meta): - verbose_name = _("TOTP device") - verbose_name_plural = _("TOTP devices") + verbose_name = _("TOTP Device") + verbose_name_plural = _("TOTP Devices") diff --git a/authentik/stages/consent/stage.py b/authentik/stages/consent/stage.py index d8c42724f..61677559c 100644 --- a/authentik/stages/consent/stage.py +++ b/authentik/stages/consent/stage.py @@ -6,11 +6,11 @@ from django.http import HttpRequest, HttpResponse from django.utils.timezone import now from rest_framework.fields import CharField +from authentik.core.api.utils import PassiveSerializer from authentik.flows.challenge import ( Challenge, ChallengeResponse, ChallengeTypes, - PermissionSerializer, WithUserInfoChallenge, ) from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER @@ -25,12 +25,19 @@ PLAN_CONTEXT_CONSENT_EXTRA_PERMISSIONS = "consent_additional_permissions" SESSION_KEY_CONSENT_TOKEN = "authentik/stages/consent/token" # nosec +class ConsentPermissionSerializer(PassiveSerializer): + """Permission used for consent""" + + name = CharField(allow_blank=True) + id = CharField() + + class ConsentChallenge(WithUserInfoChallenge): """Challenge info for consent screens""" header_text = CharField(required=False) - permissions = PermissionSerializer(many=True) - additional_permissions = PermissionSerializer(many=True) + permissions = ConsentPermissionSerializer(many=True) + additional_permissions = ConsentPermissionSerializer(many=True) component = CharField(default="ak-stage-consent") token = CharField(required=True) diff --git a/authentik/stages/deny/api.py b/authentik/stages/deny/api.py index 0cda5c214..bdadff749 100644 --- a/authentik/stages/deny/api.py +++ b/authentik/stages/deny/api.py @@ -11,7 +11,7 @@ class DenyStageSerializer(StageSerializer): class Meta: model = DenyStage - fields = StageSerializer.Meta.fields + fields = StageSerializer.Meta.fields + ["deny_message"] class DenyStageViewSet(UsedByMixin, ModelViewSet): diff --git a/authentik/stages/deny/migrations/0002_denystage_deny_message.py b/authentik/stages/deny/migrations/0002_denystage_deny_message.py new file mode 100644 index 000000000..0709c1878 --- /dev/null +++ b/authentik/stages/deny/migrations/0002_denystage_deny_message.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.6 on 2023-10-18 09:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_stages_deny", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="denystage", + name="deny_message", + field=models.TextField(blank=True, default=""), + ), + ] diff --git a/authentik/stages/deny/models.py b/authentik/stages/deny/models.py index 4aa2e692f..c5ab2c251 100644 --- a/authentik/stages/deny/models.py +++ b/authentik/stages/deny/models.py @@ -1,5 +1,5 @@ """deny stage models""" - +from django.db import models from django.utils.translation import gettext_lazy as _ from django.views import View from rest_framework.serializers import BaseSerializer @@ -10,6 +10,8 @@ from authentik.flows.models import Stage class DenyStage(Stage): """Cancels the current flow.""" + deny_message = models.TextField(blank=True, default="") + @property def serializer(self) -> type[BaseSerializer]: from authentik.stages.deny.api import DenyStageSerializer diff --git a/authentik/stages/deny/stage.py b/authentik/stages/deny/stage.py index 2b38a299e..606f504a1 100644 --- a/authentik/stages/deny/stage.py +++ b/authentik/stages/deny/stage.py @@ -2,6 +2,7 @@ from django.http import HttpRequest, HttpResponse from authentik.flows.stage import StageView +from authentik.stages.deny.models import DenyStage class DenyStageView(StageView): @@ -9,4 +10,6 @@ class DenyStageView(StageView): def dispatch(self, request: HttpRequest) -> HttpResponse: """Cancels the current flow""" - return self.executor.stage_invalid() + stage: DenyStage = self.executor.current_stage + message = self.executor.plan.context.get("deny_message", stage.deny_message) + return self.executor.stage_invalid(message) diff --git a/authentik/stages/deny/tests.py b/authentik/stages/deny/tests.py index f72b097de..17e9d49bb 100644 --- a/authentik/stages/deny/tests.py +++ b/authentik/stages/deny/tests.py @@ -45,3 +45,38 @@ class TestUserDenyStage(FlowTestCase): ) self.assertStageResponse(response, self.flow, component="ak-stage-access-denied") + + def test_message_static(self): + """Test with a static error message""" + self.stage.deny_message = "foo" + self.stage.save() + plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) + session = self.client.session + session[SESSION_KEY_PLAN] = plan + session.save() + + response = self.client.get( + reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) + ) + + self.assertStageResponse( + response, self.flow, component="ak-stage-access-denied", error_message="foo" + ) + + def test_message_overwrite(self): + """Test with an overwritten error message""" + self.stage.deny_message = "foo" + self.stage.save() + plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) + plan.context["deny_message"] = "bar" + session = self.client.session + session[SESSION_KEY_PLAN] = plan + session.save() + + response = self.client.get( + reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) + ) + + self.assertStageResponse( + response, self.flow, component="ak-stage-access-denied", error_message="bar" + ) diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py index a28fb3f39..85af0cade 100644 --- a/authentik/stages/email/stage.py +++ b/authentik/stages/email/stage.py @@ -3,8 +3,8 @@ from datetime import timedelta from django.contrib import messages from django.http import HttpRequest, HttpResponse +from django.http.request import QueryDict from django.urls import reverse -from django.utils.http import urlencode from django.utils.text import slugify from django.utils.timezone import now from django.utils.translation import gettext as _ @@ -15,7 +15,7 @@ from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTyp from authentik.flows.models import FlowDesignation, FlowToken from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import ChallengeStageView -from authentik.flows.views.executor import QS_KEY_TOKEN +from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY from authentik.stages.email.models import EmailStage from authentik.stages.email.tasks import send_mails from authentik.stages.email.utils import TemplateEmailMessage @@ -51,8 +51,22 @@ class EmailStageView(ChallengeStageView): "authentik_core:if-flow", kwargs={"flow_slug": self.executor.flow.slug}, ) - relative_url = f"{base_url}?{urlencode(kwargs)}" - return self.request.build_absolute_uri(relative_url) + # Parse query string from current URL (full query string) + query_params = QueryDict(self.request.META.get("QUERY_STRING", ""), mutable=True) + query_params.pop(QS_KEY_TOKEN, None) + + # Check for nested query string used by flow executor, and remove any + # kind of flow token from that + if QS_QUERY in query_params: + inner_query_params = QueryDict(query_params.get(QS_QUERY), mutable=True) + inner_query_params.pop(QS_KEY_TOKEN, None) + query_params[QS_QUERY] = inner_query_params.urlencode() + + query_params.update(kwargs) + full_url = base_url + if len(query_params) > 0: + full_url = f"{full_url}?{query_params.urlencode()}" + return self.request.build_absolute_uri(full_url) def get_token(self) -> FlowToken: """Get token""" @@ -146,6 +160,7 @@ class EmailStageView(ChallengeStageView): messages.error(self.request, _("No pending user.")) return super().challenge_invalid(response) self.send_email() + messages.success(self.request, _("Email Successfully sent.")) # We can't call stage_ok yet, as we're still waiting # for the user to click the link in the email return super().challenge_invalid(response) diff --git a/authentik/stages/email/tests/test_stage.py b/authentik/stages/email/tests/test_stage.py index e4b362b2a..a63d105d9 100644 --- a/authentik/stages/email/tests/test_stage.py +++ b/authentik/stages/email/tests/test_stage.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock, PropertyMock, patch from django.core import mail from django.core.mail.backends.locmem import EmailBackend from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend +from django.test import RequestFactory from django.urls import reverse from django.utils.http import urlencode @@ -12,10 +13,11 @@ from authentik.flows.markers import StageMarker from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan from authentik.flows.tests import FlowTestCase -from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_PLAN +from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_PLAN, FlowExecutorView from authentik.lib.config import CONFIG +from authentik.lib.generators import generate_id from authentik.stages.email.models import EmailStage -from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE +from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE, EmailStageView class TestEmailStage(FlowTestCase): @@ -23,8 +25,8 @@ class TestEmailStage(FlowTestCase): def setUp(self): super().setUp() + self.factory = RequestFactory() self.user = create_test_admin_user() - self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) self.stage = EmailStage.objects.create( name="email", @@ -205,3 +207,97 @@ class TestEmailStage(FlowTestCase): self.assertEqual(response.status_code, 200) self.assertStageResponse(response, component="ak-stage-access-denied") + + def test_url_no_params(self): + """Test generation of the URL in the EMail""" + 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}) + request = self.factory.get(url) + stage_view = EmailStageView( + FlowExecutorView( + request=request, + flow=self.flow, + ), + request=request, + ) + self.assertEqual(stage_view.get_full_url(), f"http://testserver/if/flow/{self.flow.slug}/") + + def test_url_our_params(self): + """Test that all of our parameters are passed to the URL correctly""" + 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}) + request = self.factory.get(url) + stage_view = EmailStageView( + FlowExecutorView( + request=request, + flow=self.flow, + ), + request=request, + ) + token = generate_id() + self.assertEqual( + stage_view.get_full_url(**{QS_KEY_TOKEN: token}), + f"http://testserver/if/flow/{self.flow.slug}/?flow_token={token}", + ) + + def test_url_existing_params(self): + """Test to ensure that URL params are preserved in the URL being sent""" + 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}) + url += "?foo=bar" + request = self.factory.get(url) + stage_view = EmailStageView( + FlowExecutorView( + request=request, + flow=self.flow, + ), + request=request, + ) + token = generate_id() + self.assertEqual( + stage_view.get_full_url(**{QS_KEY_TOKEN: token}), + f"http://testserver/if/flow/{self.flow.slug}/?foo=bar&flow_token={token}", + ) + + def test_url_existing_params_nested(self): + """Test to ensure that URL params are preserved in the URL being sent (including nested)""" + 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}) + url += "?foo=bar&" + url += "query=" + urlencode({"nested": "value"}) + request = self.factory.get(url) + stage_view = EmailStageView( + FlowExecutorView( + request=request, + flow=self.flow, + ), + request=request, + ) + token = generate_id() + self.assertEqual( + stage_view.get_full_url(**{QS_KEY_TOKEN: token}), + ( + f"http://testserver/if/flow/{self.flow.slug}" + f"/?foo=bar&query=nested%3Dvalue&flow_token={token}" + ), + ) diff --git a/authentik/stages/prompt/api.py b/authentik/stages/prompt/api.py index 255c97b4b..7d484f598 100644 --- a/authentik/stages/prompt/api.py +++ b/authentik/stages/prompt/api.py @@ -71,6 +71,7 @@ class PromptViewSet(UsedByMixin, ModelViewSet): queryset = Prompt.objects.all().prefetch_related("promptstage_set") serializer_class = PromptSerializer + ordering = ["field_key"] filterset_fields = ["field_key", "name", "label", "type", "placeholder"] search_fields = ["field_key", "name", "label", "type", "placeholder"] diff --git a/blueprints/schema.json b/blueprints/schema.json index 9dc77b796..deb177e4d 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -1188,6 +1188,43 @@ } } }, + { + "type": "object", + "required": [ + "model", + "identifiers" + ], + "properties": { + "model": { + "const": "authentik_rbac.role" + }, + "id": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "absent", + "present", + "created", + "must_created" + ], + "default": "present" + }, + "conditions": { + "type": "array", + "items": { + "type": "boolean" + } + }, + "attrs": { + "$ref": "#/$defs/model_authentik_rbac.role" + }, + "identifiers": { + "$ref": "#/$defs/model_authentik_rbac.role" + } + } + }, { "type": "object", "required": [ @@ -2705,6 +2742,43 @@ } } }, + { + "type": "object", + "required": [ + "model", + "identifiers" + ], + "properties": { + "model": { + "const": "authentik_enterprise.license" + }, + "id": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "absent", + "present", + "created", + "must_created" + ], + "default": "present" + }, + "conditions": { + "type": "array", + "items": { + "type": "boolean" + } + }, + "attrs": { + "$ref": "#/$defs/model_authentik_enterprise.license" + }, + "identifiers": { + "$ref": "#/$defs/model_authentik_enterprise.license" + } + } + }, { "type": "object", "required": [ @@ -3372,6 +3446,7 @@ "authentik.providers.radius", "authentik.providers.saml", "authentik.providers.scim", + "authentik.rbac", "authentik.recovery", "authentik.sources.ldap", "authentik.sources.oauth", @@ -3443,6 +3518,7 @@ "authentik_providers_saml.samlpropertymapping", "authentik_providers_scim.scimprovider", "authentik_providers_scim.scimmapping", + "authentik_rbac.role", "authentik_sources_ldap.ldapsource", "authentik_sources_ldap.ldappropertymapping", "authentik_sources_oauth.oauthsource", @@ -3483,7 +3559,8 @@ "authentik_core.group", "authentik_core.user", "authentik_core.application", - "authentik_core.token" + "authentik_core.token", + "authentik_enterprise.license" ], "title": "Model", "description": "Match events created by selected model. When left empty, all models are matched. When an app is selected, all the application's models are matched." @@ -4717,6 +4794,11 @@ "minLength": 1, "title": "Shared secret", "description": "Shared secret between clients and server to hash packets." + }, + "mfa_support": { + "type": "boolean", + "title": "MFA Support", + "description": "When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon." } }, "required": [] @@ -4944,6 +5026,18 @@ }, "required": [] }, + "model_authentik_rbac.role": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 150, + "minLength": 1, + "title": "Name" + } + }, + "required": [] + }, "model_authentik_sources_ldap.ldapsource": { "type": "object", "properties": { @@ -6920,6 +7014,10 @@ ] }, "title": "Flow set" + }, + "deny_message": { + "type": "string", + "title": "Deny message" } }, "required": [] @@ -8405,6 +8503,13 @@ "type": "object", "additionalProperties": true, "title": "Attributes" + }, + "roles": { + "type": "array", + "items": { + "type": "integer" + }, + "title": "Roles" } }, "required": [] @@ -8599,6 +8704,17 @@ }, "required": [] }, + "model_authentik_enterprise.license": { + "type": "object", + "properties": { + "key": { + "type": "string", + "minLength": 1, + "title": "Key" + } + }, + "required": [] + }, "model_authentik_blueprints.metaapplyblueprint": { "type": "object", "properties": { diff --git a/go.mod b/go.mod index 0b26945b5..cdde8da00 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 - goauthentik.io/api/v3 v3.2023083.6 + goauthentik.io/api/v3 v3.2023083.9 golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/oauth2 v0.13.0 golang.org/x/sync v0.4.0 diff --git a/go.sum b/go.sum index d9899e759..ee8889edb 100644 --- a/go.sum +++ b/go.sum @@ -355,8 +355,8 @@ go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyK go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -goauthentik.io/api/v3 v3.2023083.6 h1:VYVnE/3CYhggmobeZ+V3ka0TwswrUhKasxwGPmXTq0M= -goauthentik.io/api/v3 v3.2023083.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= +goauthentik.io/api/v3 v3.2023083.9 h1:23tCiPYFpxfElH7LQKiVL2zEigTefM8s4CJOlytgiAs= +goauthentik.io/api/v3 v3.2023083.9/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= diff --git a/internal/outpost/flow/solvers_mfa.go b/internal/outpost/flow/solvers_mfa.go new file mode 100644 index 000000000..f2952eb3d --- /dev/null +++ b/internal/outpost/flow/solvers_mfa.go @@ -0,0 +1,48 @@ +package flow + +import ( + "regexp" + "strconv" + "strings" +) + +const CodePasswordSeparator = ";" + +var alphaNum = regexp.MustCompile(`^[a-zA-Z0-9]*$`) + +// CheckPasswordInlineMFA For protocols that only support username/password, check if the password +// contains the TOTP code +func (fe *FlowExecutor) CheckPasswordInlineMFA() { + password := fe.Answers[StagePassword] + // We already have an authenticator answer + if fe.Answers[StageAuthenticatorValidate] != "" { + return + } + // password doesn't contain the separator + if !strings.Contains(password, CodePasswordSeparator) { + return + } + // password ends with the separator, so it won't contain an answer + if strings.HasSuffix(password, CodePasswordSeparator) { + return + } + idx := strings.LastIndex(password, CodePasswordSeparator) + authenticator := password[idx+1:] + // Authenticator is either 6 chars (totp code) or 8 chars (long totp or static) + if len(authenticator) == 6 { + // authenticator answer isn't purely numerical, so won't be value + if _, err := strconv.Atoi(authenticator); err != nil { + return + } + } else if len(authenticator) == 8 { + // 8 chars can be a long totp or static token, so it needs to be alphanumerical + if !alphaNum.MatchString(authenticator) { + return + } + } else { + // Any other length, doesn't contain an answer + return + } + fe.Answers[StagePassword] = password[:idx] + fe.Answers[StageAuthenticatorValidate] = authenticator +} diff --git a/internal/outpost/ldap/bind/direct/bind.go b/internal/outpost/ldap/bind/direct/bind.go index ce74d18f8..f6e49ccfb 100644 --- a/internal/outpost/ldap/bind/direct/bind.go +++ b/internal/outpost/ldap/bind/direct/bind.go @@ -2,9 +2,6 @@ package direct import ( "context" - "regexp" - "strconv" - "strings" "beryju.io/ldap" "github.com/getsentry/sentry-go" @@ -16,10 +13,6 @@ import ( "goauthentik.io/internal/outpost/ldap/metrics" ) -const CodePasswordSeparator = ";" - -var alphaNum = regexp.MustCompile(`^[a-zA-Z0-9]*$`) - func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResultCode, error) { fe := flow.NewFlowExecutor(req.Context(), db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{ "bindDN": req.BindDN, @@ -31,7 +24,9 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul fe.Answers[flow.StageIdentification] = username fe.Answers[flow.StagePassword] = req.BindPW - db.CheckPasswordMFA(fe) + if db.si.GetMFASupport() { + fe.CheckPasswordInlineMFA() + } passed, err := fe.Execute() flags := flags.UserFlags{ @@ -141,41 +136,3 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul uisp.Finish() return ldap.LDAPResultSuccess, nil } - -func (db *DirectBinder) CheckPasswordMFA(fe *flow.FlowExecutor) { - if !db.si.GetMFASupport() { - return - } - password := fe.Answers[flow.StagePassword] - // We already have an authenticator answer - if fe.Answers[flow.StageAuthenticatorValidate] != "" { - return - } - // password doesn't contain the separator - if !strings.Contains(password, CodePasswordSeparator) { - return - } - // password ends with the separator, so it won't contain an answer - if strings.HasSuffix(password, CodePasswordSeparator) { - return - } - idx := strings.LastIndex(password, CodePasswordSeparator) - authenticator := password[idx+1:] - // Authenticator is either 6 chars (totp code) or 8 chars (long totp or static) - if len(authenticator) == 6 { - // authenticator answer isn't purely numerical, so won't be value - if _, err := strconv.Atoi(authenticator); err != nil { - return - } - } else if len(authenticator) == 8 { - // 8 chars can be a long totp or static token, so it needs to be alphanumerical - if !alphaNum.MatchString(authenticator) { - return - } - } else { - // Any other length, doesn't contain an answer - return - } - fe.Answers[flow.StagePassword] = password[:idx] - fe.Answers[flow.StageAuthenticatorValidate] = authenticator -} diff --git a/internal/outpost/ldap/search/memory/memory.go b/internal/outpost/ldap/search/memory/memory.go index d877b76e5..177099f7e 100644 --- a/internal/outpost/ldap/search/memory/memory.go +++ b/internal/outpost/ldap/search/memory/memory.go @@ -162,7 +162,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, for _, u := range g.UsersObj { if flag.UserPk == u.Pk { //TODO: Is there a better way to clone this object? - fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}) + fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{}) fg.SetUsers([]int32{flag.UserPk}) if g.Parent.IsSet() { fg.SetParent(*g.Parent.Get()) diff --git a/internal/outpost/radius/api.go b/internal/outpost/radius/api.go index 5092a9cfb..ce35e7096 100644 --- a/internal/outpost/radius/api.go +++ b/internal/outpost/radius/api.go @@ -40,11 +40,10 @@ func (rs *RadiusServer) Refresh() error { providers := make([]*ProviderInstance, len(outposts.Results)) for idx, provider := range outposts.Results { logger := log.WithField("logger", "authentik.outpost.radius").WithField("provider", provider.Name) - s := *provider.SharedSecret - c := *provider.ClientNetworks providers[idx] = &ProviderInstance{ - SharedSecret: []byte(s), - ClientNetworks: parseCIDRs(c), + SharedSecret: []byte(provider.GetSharedSecret()), + ClientNetworks: parseCIDRs(provider.GetClientNetworks()), + MFASupport: provider.GetMfaSupport(), appSlug: provider.ApplicationSlug, flowSlug: provider.AuthFlowSlug, s: rs, diff --git a/internal/outpost/radius/handle_access_request.go b/internal/outpost/radius/handle_access_request.go index 465f1d267..6ea387f53 100644 --- a/internal/outpost/radius/handle_access_request.go +++ b/internal/outpost/radius/handle_access_request.go @@ -22,6 +22,9 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR fe.Answers[flow.StageIdentification] = username fe.Answers[flow.StagePassword] = rfc2865.UserPassword_GetString(r.Packet) + if r.pi.MFASupport { + fe.CheckPasswordInlineMFA() + } passed, err := fe.Execute() diff --git a/internal/outpost/radius/radius.go b/internal/outpost/radius/radius.go index d78854e4a..808336e57 100644 --- a/internal/outpost/radius/radius.go +++ b/internal/outpost/radius/radius.go @@ -17,6 +17,7 @@ import ( type ProviderInstance struct { ClientNetworks []*net.IPNet SharedSecret []byte + MFASupport bool appSlug string flowSlug string diff --git a/lifecycle/migrate.py b/lifecycle/migrate.py index 68d45dbc3..4476a4681 100755 --- a/lifecycle/migrate.py +++ b/lifecycle/migrate.py @@ -81,8 +81,8 @@ if __name__ == "__main__": ) curr = conn.cursor() try: - for migration in Path(__file__).parent.absolute().glob("system_migrations/*.py"): - spec = spec_from_file_location("lifecycle.system_migrations", migration) + for migration_path in Path(__file__).parent.absolute().glob("system_migrations/*.py"): + spec = spec_from_file_location("lifecycle.system_migrations", migration_path) if not spec: continue mod = module_from_spec(spec) @@ -94,9 +94,9 @@ if __name__ == "__main__": migration = sub(curr, conn) if migration.needs_migration(): wait_for_lock() - LOGGER.info("Migration needs to be applied", migration=sub) + LOGGER.info("Migration needs to be applied", migration=migration_path.name) migration.run() - LOGGER.info("Migration finished applying", migration=sub) + LOGGER.info("Migration finished applying", migration=migration_path.name) release_lock() LOGGER.info("applying django migrations") environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings") diff --git a/lifecycle/system_migrations/to_0_10.py b/lifecycle/system_migrations/to_0_10.py index 84ab45b39..ebad5c70a 100644 --- a/lifecycle/system_migrations/to_0_10.py +++ b/lifecycle/system_migrations/to_0_10.py @@ -2,6 +2,7 @@ from lifecycle.migrate import BaseMigration SQL_STATEMENT = """ +BEGIN TRANSACTION; DELETE FROM django_migrations WHERE app = 'passbook_stages_prompt'; DROP TABLE passbook_stages_prompt_prompt cascade; DROP TABLE passbook_stages_prompt_promptstage cascade; @@ -22,6 +23,7 @@ DELETE FROM django_migrations WHERE app = 'passbook_flows' AND name = '0008_defa DELETE FROM django_migrations WHERE app = 'passbook_flows' AND name = '0009_source_flows'; DELETE FROM django_migrations WHERE app = 'passbook_flows' AND name = '0010_provider_flows'; DELETE FROM django_migrations WHERE app = 'passbook_stages_password' AND name = '0002_passwordstage_change_flow'; +COMMIT; """ diff --git a/lifecycle/system_migrations/to_0_13_authentik.py b/lifecycle/system_migrations/to_0_13_authentik.py index 8ba702132..b621859d7 100644 --- a/lifecycle/system_migrations/to_0_13_authentik.py +++ b/lifecycle/system_migrations/to_0_13_authentik.py @@ -4,7 +4,7 @@ from redis import Redis from authentik.lib.config import CONFIG from lifecycle.migrate import BaseMigration -SQL_STATEMENT = """ +SQL_STATEMENT = """BEGIN TRANSACTION; ALTER TABLE passbook_audit_event RENAME TO authentik_audit_event; ALTER TABLE passbook_core_application RENAME TO authentik_core_application; ALTER TABLE passbook_core_group RENAME TO authentik_core_group; @@ -92,6 +92,7 @@ ALTER SEQUENCE passbook_stages_prompt_promptstage_validation_policies_id_seq REN UPDATE django_migrations SET app = replace(app, 'passbook', 'authentik'); UPDATE django_content_type SET app_label = replace(app_label, 'passbook', 'authentik'); +COMMIT; """ diff --git a/lifecycle/system_migrations/to_0_14_events..py b/lifecycle/system_migrations/to_0_14_events.py similarity index 78% rename from lifecycle/system_migrations/to_0_14_events..py rename to lifecycle/system_migrations/to_0_14_events.py index b1a0cc727..9a7b14979 100644 --- a/lifecycle/system_migrations/to_0_14_events..py +++ b/lifecycle/system_migrations/to_0_14_events.py @@ -1,9 +1,12 @@ # flake8: noqa from lifecycle.migrate import BaseMigration -SQL_STATEMENT = """ALTER TABLE authentik_audit_event RENAME TO authentik_events_event; +SQL_STATEMENT = """BEGIN TRANSACTION; +ALTER TABLE authentik_audit_event RENAME TO authentik_events_event; UPDATE django_migrations SET app = replace(app, 'authentik_audit', 'authentik_events'); -UPDATE django_content_type SET app_label = replace(app_label, 'authentik_audit', 'authentik_events');""" +UPDATE django_content_type SET app_label = replace(app_label, 'authentik_audit', 'authentik_events'); + +COMMIT;""" class Migration(BaseMigration): diff --git a/lifecycle/system_migrations/to_2021_3_authenticator.py b/lifecycle/system_migrations/to_2021_3_authenticator.py index 3b633fef1..52a870ba2 100644 --- a/lifecycle/system_migrations/to_2021_3_authenticator.py +++ b/lifecycle/system_migrations/to_2021_3_authenticator.py @@ -2,6 +2,7 @@ from lifecycle.migrate import BaseMigration SQL_STATEMENT = """ +BEGIN TRANSACTION; ALTER TABLE authentik_stages_otp_static_otpstaticstage RENAME TO authentik_stages_authenticator_static_otpstaticstage; UPDATE django_migrations SET app = replace(app, 'authentik_stages_otp_static', 'authentik_stages_authenticator_static'); UPDATE django_content_type SET app_label = replace(app_label, 'authentik_stages_otp_static', 'authentik_stages_authenticator_static'); @@ -13,6 +14,7 @@ UPDATE django_content_type SET app_label = replace(app_label, 'authentik_stages_ ALTER TABLE authentik_stages_otp_validate_otpvalidatestage RENAME TO authentik_stages_authenticator_validate_otpvalidatestage; UPDATE django_migrations SET app = replace(app, 'authentik_stages_otp_validate', 'authentik_stages_authenticator_validate'); UPDATE django_content_type SET app_label = replace(app_label, 'authentik_stages_otp_validate', 'authentik_stages_authenticator_validate'); +COMMIT; """ diff --git a/lifecycle/system_migrations/to_2023_1_hibp_remove.py b/lifecycle/system_migrations/to_2023_1_hibp_remove.py index c43f6bb85..92ec2e1f6 100644 --- a/lifecycle/system_migrations/to_2023_1_hibp_remove.py +++ b/lifecycle/system_migrations/to_2023_1_hibp_remove.py @@ -1,8 +1,11 @@ # flake8: noqa from lifecycle.migrate import BaseMigration -SQL_STATEMENT = """DROP TABLE "authentik_policies_hibp_haveibeenpwendpolicy"; -DELETE FROM django_migrations WHERE app = 'authentik_policies_hibp';""" +SQL_STATEMENT = """ +BEGIN TRANSACTION; +DROP TABLE "authentik_policies_hibp_haveibeenpwendpolicy"; +DELETE FROM django_migrations WHERE app = 'authentik_policies_hibp'; +COMMIT;""" class Migration(BaseMigration): diff --git a/poetry.lock b/poetry.lock index d509b16d1..dbe6c95c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -437,33 +437,29 @@ files = [ [[package]] name = "black" -version = "23.9.1" +version = "23.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, - {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, - {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, - {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, - {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, - {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, - {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, - {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, - {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, - {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, - {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, + {file = "black-23.10.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:f8dc7d50d94063cdfd13c82368afd8588bac4ce360e4224ac399e769d6704e98"}, + {file = "black-23.10.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:f20ff03f3fdd2fd4460b4f631663813e57dc277e37fb216463f3b907aa5a9bdd"}, + {file = "black-23.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3d9129ce05b0829730323bdcb00f928a448a124af5acf90aa94d9aba6969604"}, + {file = "black-23.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:960c21555be135c4b37b7018d63d6248bdae8514e5c55b71e994ad37407f45b8"}, + {file = "black-23.10.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:30b78ac9b54cf87bcb9910ee3d499d2bc893afd52495066c49d9ee6b21eee06e"}, + {file = "black-23.10.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:0e232f24a337fed7a82c1185ae46c56c4a6167fb0fe37411b43e876892c76699"}, + {file = "black-23.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31946ec6f9c54ed7ba431c38bc81d758970dd734b96b8e8c2b17a367d7908171"}, + {file = "black-23.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:c870bee76ad5f7a5ea7bd01dc646028d05568d33b0b09b7ecfc8ec0da3f3f39c"}, + {file = "black-23.10.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:6901631b937acbee93c75537e74f69463adaf34379a04eef32425b88aca88a23"}, + {file = "black-23.10.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:481167c60cd3e6b1cb8ef2aac0f76165843a374346aeeaa9d86765fe0dd0318b"}, + {file = "black-23.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74892b4b836e5162aa0452393112a574dac85e13902c57dfbaaf388e4eda37c"}, + {file = "black-23.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:47c4510f70ec2e8f9135ba490811c071419c115e46f143e4dce2ac45afdcf4c9"}, + {file = "black-23.10.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:76baba9281e5e5b230c9b7f83a96daf67a95e919c2dfc240d9e6295eab7b9204"}, + {file = "black-23.10.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:a3c2ddb35f71976a4cfeca558848c2f2f89abc86b06e8dd89b5a65c1e6c0f22a"}, + {file = "black-23.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db451a3363b1e765c172c3fd86213a4ce63fb8524c938ebd82919bf2a6e28c6a"}, + {file = "black-23.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:7fb5fc36bb65160df21498d5a3dd330af8b6401be3f25af60c6ebfe23753f747"}, + {file = "black-23.10.0-py3-none-any.whl", hash = "sha256:e223b731a0e025f8ef427dd79d8cd69c167da807f5710add30cdf131f13dd62e"}, + {file = "black-23.10.0.tar.gz", hash = "sha256:31b9f87b277a68d0e99d2905edae08807c007973eaa609da5f0c62def6b7c0bd"}, ] [package.dependencies] @@ -3425,28 +3421,28 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.0.292" +version = "0.1.0" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"}, - {file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c3c91859a9b845c33778f11902e7b26440d64b9d5110edd4e4fa1726c41e0a4"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4476f1243af2d8c29da5f235c13dca52177117935e1f9393f9d90f9833f69e4"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8eb50eaf8648070b8e58ece8e69c9322d34afe367eec4210fdee9a555e4ca7"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9889bac18a0c07018aac75ef6c1e6511d8411724d67cb879103b01758e110a81"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdfabd4334684a4418b99b3118793f2c13bb67bf1540a769d7816410402a205"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7c77c53bfcd75dbcd4d1f42d6cabf2485d2e1ee0678da850f08e1ab13081a8"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e087b24d0d849c5c81516ec740bf4fd48bf363cfb104545464e0fca749b6af9"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f160b5ec26be32362d0774964e218f3fcf0a7da299f7e220ef45ae9e3e67101a"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ac153eee6dd4444501c4bb92bff866491d4bfb01ce26dd2fff7ca472c8df9ad0"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87616771e72820800b8faea82edd858324b29bb99a920d6aa3d3949dd3f88fb0"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b76deb3bdbea2ef97db286cf953488745dd6424c122d275f05836c53f62d4016"}, - {file = "ruff-0.0.292-py3-none-win32.whl", hash = "sha256:e854b05408f7a8033a027e4b1c7f9889563dd2aca545d13d06711e5c39c3d003"}, - {file = "ruff-0.0.292-py3-none-win_amd64.whl", hash = "sha256:f27282bedfd04d4c3492e5c3398360c9d86a295be00eccc63914438b4ac8a83c"}, - {file = "ruff-0.0.292-py3-none-win_arm64.whl", hash = "sha256:7f67a69c8f12fbc8daf6ae6d36705037bde315abf8b82b6e1f4c9e74eb750f68"}, - {file = "ruff-0.0.292.tar.gz", hash = "sha256:1093449e37dd1e9b813798f6ad70932b57cf614e5c2b5c51005bf67d55db33ac"}, + {file = "ruff-0.1.0-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:87114e254dee35e069e1b922d85d4b21a5b61aec759849f393e1dbb308a00439"}, + {file = "ruff-0.1.0-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:764f36d2982cc4a703e69fb73a280b7c539fd74b50c9ee531a4e3fe88152f521"}, + {file = "ruff-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65f4b7fb539e5cf0f71e9bd74f8ddab74cabdd673c6fb7f17a4dcfd29f126255"}, + {file = "ruff-0.1.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:299fff467a0f163baa282266b310589b21400de0a42d8f68553422fa6bf7ee01"}, + {file = "ruff-0.1.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d412678bf205787263bb702c984012a4f97e460944c072fd7cfa2bd084857c4"}, + {file = "ruff-0.1.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a5391b49b1669b540924640587d8d24128e45be17d1a916b1801d6645e831581"}, + {file = "ruff-0.1.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee8cd57f454cdd77bbcf1e11ff4e0046fb6547cac1922cc6e3583ce4b9c326d1"}, + {file = "ruff-0.1.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa7aeed7bc23861a2b38319b636737bf11cfa55d2109620b49cf995663d3e888"}, + {file = "ruff-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04cd4298b43b16824d9a37800e4c145ba75c29c43ce0d74cad1d66d7ae0a4c5"}, + {file = "ruff-0.1.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7186ccf54707801d91e6314a016d1c7895e21d2e4cd614500d55870ed983aa9f"}, + {file = "ruff-0.1.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d88adfd93849bc62449518228581d132e2023e30ebd2da097f73059900d8dce3"}, + {file = "ruff-0.1.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ad2ccdb3bad5a61013c76a9c1240fdfadf2c7103a2aeebd7bcbbed61f363138f"}, + {file = "ruff-0.1.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b77f6cfa72c6eb19b5cac967cc49762ae14d036db033f7d97a72912770fd8e1c"}, + {file = "ruff-0.1.0-py3-none-win32.whl", hash = "sha256:480bd704e8af1afe3fd444cc52e3c900b936e6ca0baf4fb0281124330b6ceba2"}, + {file = "ruff-0.1.0-py3-none-win_amd64.whl", hash = "sha256:a76ba81860f7ee1f2d5651983f87beb835def94425022dc5f0803108f1b8bfa2"}, + {file = "ruff-0.1.0-py3-none-win_arm64.whl", hash = "sha256:45abdbdab22509a2c6052ecf7050b3f5c7d6b7898dc07e82869401b531d46da4"}, + {file = "ruff-0.1.0.tar.gz", hash = "sha256:ad6b13824714b19c5f8225871cf532afb994470eecb74631cd3500fe817e6b3f"}, ] [[package]] @@ -3871,13 +3867,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.6" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, - {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.dependencies] diff --git a/schema.yml b/schema.yml index 03eed6d01..a311785f0 100644 --- a/schema.yml +++ b/schema.yml @@ -885,7 +885,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -918,7 +918,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -957,7 +957,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -995,7 +995,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -1113,7 +1113,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -1146,7 +1146,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -1185,7 +1185,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -1223,7 +1223,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -2030,7 +2030,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -2063,7 +2063,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -2102,7 +2102,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -2140,7 +2140,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -2170,7 +2170,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this Static device. + description: A unique integer value identifying this Static Device. required: true tags: - authenticators @@ -2262,7 +2262,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -2295,7 +2295,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -2334,7 +2334,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -2372,7 +2372,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -2402,7 +2402,7 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this TOTP device. + description: A unique integer value identifying this TOTP Device. required: true tags: - authenticators @@ -3379,7 +3379,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this group. + description: A UUID string identifying this Group. required: true tags: - core @@ -3413,7 +3413,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this group. + description: A UUID string identifying this Group. required: true tags: - core @@ -3453,7 +3453,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this group. + description: A UUID string identifying this Group. required: true tags: - core @@ -3492,7 +3492,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this group. + description: A UUID string identifying this Group. required: true tags: - core @@ -3523,7 +3523,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this group. + description: A UUID string identifying this Group. required: true tags: - core @@ -3562,7 +3562,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this group. + description: A UUID string identifying this Group. required: true tags: - core @@ -3601,7 +3601,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this group. + description: A UUID string identifying this Group. required: true tags: - core @@ -5653,7 +5653,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this license. + description: A UUID string identifying this License. required: true tags: - enterprise @@ -5687,7 +5687,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this license. + description: A UUID string identifying this License. required: true tags: - enterprise @@ -5727,7 +5727,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this license. + description: A UUID string identifying this License. required: true tags: - enterprise @@ -5766,7 +5766,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this license. + description: A UUID string identifying this License. required: true tags: - enterprise @@ -5797,7 +5797,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this license. + description: A UUID string identifying this License. required: true tags: - enterprise @@ -9120,7 +9120,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this outpost. + description: A UUID string identifying this Outpost. required: true tags: - outposts @@ -9154,7 +9154,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this outpost. + description: A UUID string identifying this Outpost. required: true tags: - outposts @@ -9194,7 +9194,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this outpost. + description: A UUID string identifying this Outpost. required: true tags: - outposts @@ -9233,7 +9233,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this outpost. + description: A UUID string identifying this Outpost. required: true tags: - outposts @@ -9312,7 +9312,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this outpost. + description: A UUID string identifying this Outpost. required: true tags: - outposts @@ -9349,7 +9349,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this outpost. + description: A UUID string identifying this Outpost. required: true tags: - outposts @@ -17051,6 +17051,1068 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' + /rbac/permissions/: + get: + operationId: rbac_permissions_list + description: Read-only list of all permissions, filterable by model and app + parameters: + - in: query + name: codename + schema: + type: string + - in: query + name: content_type__app_label + schema: + type: string + - in: query + name: content_type__model + schema: + type: string + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: role + schema: + type: string + - name: search + required: false + in: query + description: A search term. + schema: + type: string + - in: query + name: user + schema: + type: integer + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPermissionList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/{id}/: + get: + operationId: rbac_permissions_retrieve + description: Read-only list of all permissions, filterable by model and app + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this permission. + required: true + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Permission' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/assigned_by_roles/: + get: + operationId: rbac_permissions_assigned_by_roles_list + description: Get assigned object permissions for a single object + parameters: + - in: query + name: model + schema: + type: string + enum: + - authentik_blueprints.blueprintinstance + - authentik_core.application + - authentik_core.group + - authentik_core.token + - authentik_core.user + - authentik_crypto.certificatekeypair + - authentik_enterprise.license + - authentik_events.event + - authentik_events.notification + - authentik_events.notificationrule + - authentik_events.notificationtransport + - authentik_events.notificationwebhookmapping + - authentik_flows.flow + - authentik_flows.flowstagebinding + - authentik_outposts.dockerserviceconnection + - authentik_outposts.kubernetesserviceconnection + - authentik_outposts.outpost + - authentik_policies.policybinding + - authentik_policies_dummy.dummypolicy + - authentik_policies_event_matcher.eventmatcherpolicy + - authentik_policies_expiry.passwordexpirypolicy + - authentik_policies_expression.expressionpolicy + - authentik_policies_password.passwordpolicy + - authentik_policies_reputation.reputation + - authentik_policies_reputation.reputationpolicy + - authentik_providers_ldap.ldapprovider + - authentik_providers_oauth2.accesstoken + - authentik_providers_oauth2.authorizationcode + - authentik_providers_oauth2.oauth2provider + - authentik_providers_oauth2.refreshtoken + - authentik_providers_oauth2.scopemapping + - authentik_providers_proxy.proxyprovider + - authentik_providers_radius.radiusprovider + - authentik_providers_saml.samlpropertymapping + - authentik_providers_saml.samlprovider + - authentik_providers_scim.scimmapping + - authentik_providers_scim.scimprovider + - authentik_rbac.role + - authentik_sources_ldap.ldappropertymapping + - authentik_sources_ldap.ldapsource + - authentik_sources_oauth.oauthsource + - authentik_sources_oauth.useroauthsourceconnection + - authentik_sources_plex.plexsource + - authentik_sources_plex.plexsourceconnection + - authentik_sources_saml.samlsource + - authentik_sources_saml.usersamlsourceconnection + - authentik_stages_authenticator_duo.authenticatorduostage + - authentik_stages_authenticator_duo.duodevice + - authentik_stages_authenticator_sms.authenticatorsmsstage + - authentik_stages_authenticator_sms.smsdevice + - authentik_stages_authenticator_static.authenticatorstaticstage + - authentik_stages_authenticator_static.staticdevice + - authentik_stages_authenticator_totp.authenticatortotpstage + - authentik_stages_authenticator_totp.totpdevice + - authentik_stages_authenticator_validate.authenticatorvalidatestage + - authentik_stages_authenticator_webauthn.authenticatewebauthnstage + - authentik_stages_authenticator_webauthn.webauthndevice + - authentik_stages_captcha.captchastage + - authentik_stages_consent.consentstage + - authentik_stages_consent.userconsent + - authentik_stages_deny.denystage + - authentik_stages_dummy.dummystage + - authentik_stages_email.emailstage + - authentik_stages_identification.identificationstage + - authentik_stages_invitation.invitation + - authentik_stages_invitation.invitationstage + - authentik_stages_password.passwordstage + - authentik_stages_prompt.prompt + - authentik_stages_prompt.promptstage + - authentik_stages_user_delete.userdeletestage + - authentik_stages_user_login.userloginstage + - authentik_stages_user_logout.userlogoutstage + - authentik_stages_user_write.userwritestage + - authentik_tenants.tenant + description: |- + * `authentik_crypto.certificatekeypair` - Certificate-Key Pair + * `authentik_events.event` - Event + * `authentik_events.notificationtransport` - Notification Transport + * `authentik_events.notification` - Notification + * `authentik_events.notificationrule` - Notification Rule + * `authentik_events.notificationwebhookmapping` - Webhook Mapping + * `authentik_flows.flow` - Flow + * `authentik_flows.flowstagebinding` - Flow Stage Binding + * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection + * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection + * `authentik_outposts.outpost` - Outpost + * `authentik_policies_dummy.dummypolicy` - Dummy Policy + * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy + * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy + * `authentik_policies_expression.expressionpolicy` - Expression Policy + * `authentik_policies_password.passwordpolicy` - Password Policy + * `authentik_policies_reputation.reputationpolicy` - Reputation Policy + * `authentik_policies_reputation.reputation` - Reputation Score + * `authentik_policies.policybinding` - Policy Binding + * `authentik_providers_ldap.ldapprovider` - LDAP Provider + * `authentik_providers_oauth2.scopemapping` - Scope Mapping + * `authentik_providers_oauth2.oauth2provider` - OAuth2/OpenID Provider + * `authentik_providers_oauth2.authorizationcode` - Authorization Code + * `authentik_providers_oauth2.accesstoken` - OAuth2 Access Token + * `authentik_providers_oauth2.refreshtoken` - OAuth2 Refresh Token + * `authentik_providers_proxy.proxyprovider` - Proxy Provider + * `authentik_providers_radius.radiusprovider` - Radius Provider + * `authentik_providers_saml.samlprovider` - SAML Provider + * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping + * `authentik_providers_scim.scimprovider` - SCIM Provider + * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_rbac.role` - Role + * `authentik_sources_ldap.ldapsource` - LDAP Source + * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping + * `authentik_sources_oauth.oauthsource` - OAuth Source + * `authentik_sources_oauth.useroauthsourceconnection` - User OAuth Source Connection + * `authentik_sources_plex.plexsource` - Plex Source + * `authentik_sources_plex.plexsourceconnection` - User Plex Source Connection + * `authentik_sources_saml.samlsource` - SAML Source + * `authentik_sources_saml.usersamlsourceconnection` - User SAML Source Connection + * `authentik_stages_authenticator_duo.authenticatorduostage` - Duo Authenticator Setup Stage + * `authentik_stages_authenticator_duo.duodevice` - Duo Device + * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage + * `authentik_stages_authenticator_sms.smsdevice` - SMS Device + * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage + * `authentik_stages_authenticator_static.staticdevice` - Static Device + * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage + * `authentik_stages_authenticator_totp.totpdevice` - TOTP Device + * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage + * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage + * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device + * `authentik_stages_captcha.captchastage` - Captcha Stage + * `authentik_stages_consent.consentstage` - Consent Stage + * `authentik_stages_consent.userconsent` - User Consent + * `authentik_stages_deny.denystage` - Deny Stage + * `authentik_stages_dummy.dummystage` - Dummy Stage + * `authentik_stages_email.emailstage` - Email Stage + * `authentik_stages_identification.identificationstage` - Identification Stage + * `authentik_stages_invitation.invitationstage` - Invitation Stage + * `authentik_stages_invitation.invitation` - Invitation + * `authentik_stages_password.passwordstage` - Password Stage + * `authentik_stages_prompt.prompt` - Prompt + * `authentik_stages_prompt.promptstage` - Prompt Stage + * `authentik_stages_user_delete.userdeletestage` - User Delete Stage + * `authentik_stages_user_login.userloginstage` - User Login Stage + * `authentik_stages_user_logout.userlogoutstage` - User Logout Stage + * `authentik_stages_user_write.userwritestage` - User Write Stage + * `authentik_tenants.tenant` - Tenant + * `authentik_blueprints.blueprintinstance` - Blueprint Instance + * `authentik_core.group` - Group + * `authentik_core.user` - User + * `authentik_core.application` - Application + * `authentik_core.token` - Token + * `authentik_enterprise.license` - License + required: true + - in: query + name: object_pk + schema: + type: string + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRoleAssignedObjectPermissionList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/assigned_by_roles/{uuid}/assign/: + post: + operationId: rbac_permissions_assigned_by_roles_assign_create + description: |- + Assign permission(s) to role. When `object_pk` is set, the permissions + are only assigned to the specific object, otherwise they are assigned globally. + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Role. + required: true + tags: + - rbac + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PermissionAssignRequest' + required: true + security: + - authentik: [] + responses: + '204': + description: Successfully assigned + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/assigned_by_roles/{uuid}/unassign/: + patch: + operationId: rbac_permissions_assigned_by_roles_unassign_partial_update + description: |- + Unassign permission(s) to role. When `object_pk` is set, the permissions + are only assigned to the specific object, otherwise they are assigned globally. + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Role. + required: true + tags: + - rbac + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPermissionAssignRequest' + security: + - authentik: [] + responses: + '204': + description: Successfully unassigned + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/assigned_by_users/: + get: + operationId: rbac_permissions_assigned_by_users_list + description: Get assigned object permissions for a single object + parameters: + - in: query + name: model + schema: + type: string + enum: + - authentik_blueprints.blueprintinstance + - authentik_core.application + - authentik_core.group + - authentik_core.token + - authentik_core.user + - authentik_crypto.certificatekeypair + - authentik_enterprise.license + - authentik_events.event + - authentik_events.notification + - authentik_events.notificationrule + - authentik_events.notificationtransport + - authentik_events.notificationwebhookmapping + - authentik_flows.flow + - authentik_flows.flowstagebinding + - authentik_outposts.dockerserviceconnection + - authentik_outposts.kubernetesserviceconnection + - authentik_outposts.outpost + - authentik_policies.policybinding + - authentik_policies_dummy.dummypolicy + - authentik_policies_event_matcher.eventmatcherpolicy + - authentik_policies_expiry.passwordexpirypolicy + - authentik_policies_expression.expressionpolicy + - authentik_policies_password.passwordpolicy + - authentik_policies_reputation.reputation + - authentik_policies_reputation.reputationpolicy + - authentik_providers_ldap.ldapprovider + - authentik_providers_oauth2.accesstoken + - authentik_providers_oauth2.authorizationcode + - authentik_providers_oauth2.oauth2provider + - authentik_providers_oauth2.refreshtoken + - authentik_providers_oauth2.scopemapping + - authentik_providers_proxy.proxyprovider + - authentik_providers_radius.radiusprovider + - authentik_providers_saml.samlpropertymapping + - authentik_providers_saml.samlprovider + - authentik_providers_scim.scimmapping + - authentik_providers_scim.scimprovider + - authentik_rbac.role + - authentik_sources_ldap.ldappropertymapping + - authentik_sources_ldap.ldapsource + - authentik_sources_oauth.oauthsource + - authentik_sources_oauth.useroauthsourceconnection + - authentik_sources_plex.plexsource + - authentik_sources_plex.plexsourceconnection + - authentik_sources_saml.samlsource + - authentik_sources_saml.usersamlsourceconnection + - authentik_stages_authenticator_duo.authenticatorduostage + - authentik_stages_authenticator_duo.duodevice + - authentik_stages_authenticator_sms.authenticatorsmsstage + - authentik_stages_authenticator_sms.smsdevice + - authentik_stages_authenticator_static.authenticatorstaticstage + - authentik_stages_authenticator_static.staticdevice + - authentik_stages_authenticator_totp.authenticatortotpstage + - authentik_stages_authenticator_totp.totpdevice + - authentik_stages_authenticator_validate.authenticatorvalidatestage + - authentik_stages_authenticator_webauthn.authenticatewebauthnstage + - authentik_stages_authenticator_webauthn.webauthndevice + - authentik_stages_captcha.captchastage + - authentik_stages_consent.consentstage + - authentik_stages_consent.userconsent + - authentik_stages_deny.denystage + - authentik_stages_dummy.dummystage + - authentik_stages_email.emailstage + - authentik_stages_identification.identificationstage + - authentik_stages_invitation.invitation + - authentik_stages_invitation.invitationstage + - authentik_stages_password.passwordstage + - authentik_stages_prompt.prompt + - authentik_stages_prompt.promptstage + - authentik_stages_user_delete.userdeletestage + - authentik_stages_user_login.userloginstage + - authentik_stages_user_logout.userlogoutstage + - authentik_stages_user_write.userwritestage + - authentik_tenants.tenant + description: |- + * `authentik_crypto.certificatekeypair` - Certificate-Key Pair + * `authentik_events.event` - Event + * `authentik_events.notificationtransport` - Notification Transport + * `authentik_events.notification` - Notification + * `authentik_events.notificationrule` - Notification Rule + * `authentik_events.notificationwebhookmapping` - Webhook Mapping + * `authentik_flows.flow` - Flow + * `authentik_flows.flowstagebinding` - Flow Stage Binding + * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection + * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection + * `authentik_outposts.outpost` - Outpost + * `authentik_policies_dummy.dummypolicy` - Dummy Policy + * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy + * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy + * `authentik_policies_expression.expressionpolicy` - Expression Policy + * `authentik_policies_password.passwordpolicy` - Password Policy + * `authentik_policies_reputation.reputationpolicy` - Reputation Policy + * `authentik_policies_reputation.reputation` - Reputation Score + * `authentik_policies.policybinding` - Policy Binding + * `authentik_providers_ldap.ldapprovider` - LDAP Provider + * `authentik_providers_oauth2.scopemapping` - Scope Mapping + * `authentik_providers_oauth2.oauth2provider` - OAuth2/OpenID Provider + * `authentik_providers_oauth2.authorizationcode` - Authorization Code + * `authentik_providers_oauth2.accesstoken` - OAuth2 Access Token + * `authentik_providers_oauth2.refreshtoken` - OAuth2 Refresh Token + * `authentik_providers_proxy.proxyprovider` - Proxy Provider + * `authentik_providers_radius.radiusprovider` - Radius Provider + * `authentik_providers_saml.samlprovider` - SAML Provider + * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping + * `authentik_providers_scim.scimprovider` - SCIM Provider + * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_rbac.role` - Role + * `authentik_sources_ldap.ldapsource` - LDAP Source + * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping + * `authentik_sources_oauth.oauthsource` - OAuth Source + * `authentik_sources_oauth.useroauthsourceconnection` - User OAuth Source Connection + * `authentik_sources_plex.plexsource` - Plex Source + * `authentik_sources_plex.plexsourceconnection` - User Plex Source Connection + * `authentik_sources_saml.samlsource` - SAML Source + * `authentik_sources_saml.usersamlsourceconnection` - User SAML Source Connection + * `authentik_stages_authenticator_duo.authenticatorduostage` - Duo Authenticator Setup Stage + * `authentik_stages_authenticator_duo.duodevice` - Duo Device + * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage + * `authentik_stages_authenticator_sms.smsdevice` - SMS Device + * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage + * `authentik_stages_authenticator_static.staticdevice` - Static Device + * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage + * `authentik_stages_authenticator_totp.totpdevice` - TOTP Device + * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage + * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage + * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device + * `authentik_stages_captcha.captchastage` - Captcha Stage + * `authentik_stages_consent.consentstage` - Consent Stage + * `authentik_stages_consent.userconsent` - User Consent + * `authentik_stages_deny.denystage` - Deny Stage + * `authentik_stages_dummy.dummystage` - Dummy Stage + * `authentik_stages_email.emailstage` - Email Stage + * `authentik_stages_identification.identificationstage` - Identification Stage + * `authentik_stages_invitation.invitationstage` - Invitation Stage + * `authentik_stages_invitation.invitation` - Invitation + * `authentik_stages_password.passwordstage` - Password Stage + * `authentik_stages_prompt.prompt` - Prompt + * `authentik_stages_prompt.promptstage` - Prompt Stage + * `authentik_stages_user_delete.userdeletestage` - User Delete Stage + * `authentik_stages_user_login.userloginstage` - User Login Stage + * `authentik_stages_user_logout.userlogoutstage` - User Logout Stage + * `authentik_stages_user_write.userwritestage` - User Write Stage + * `authentik_tenants.tenant` - Tenant + * `authentik_blueprints.blueprintinstance` - Blueprint Instance + * `authentik_core.group` - Group + * `authentik_core.user` - User + * `authentik_core.application` - Application + * `authentik_core.token` - Token + * `authentik_enterprise.license` - License + required: true + - in: query + name: object_pk + schema: + type: string + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedUserAssignedObjectPermissionList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/assigned_by_users/{id}/assign/: + post: + operationId: rbac_permissions_assigned_by_users_assign_create + description: Assign permission(s) to user + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - rbac + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PermissionAssignRequest' + required: true + security: + - authentik: [] + responses: + '204': + description: Successfully assigned + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/assigned_by_users/{id}/unassign/: + patch: + operationId: rbac_permissions_assigned_by_users_unassign_partial_update + description: |- + Unassign permission(s) to user. When `object_pk` is set, the permissions + are only assigned to the specific object, otherwise they are assigned globally. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - rbac + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPermissionAssignRequest' + security: + - authentik: [] + responses: + '204': + description: Successfully unassigned + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/roles/: + get: + operationId: rbac_permissions_roles_list + description: Get a role's assigned object permissions + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + - in: query + name: uuid + schema: + type: string + format: uuid + required: true + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedExtraRoleObjectPermissionList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/permissions/users/: + get: + operationId: rbac_permissions_users_list + description: Get a users's assigned object permissions + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + - in: query + name: user_id + schema: + type: integer + required: true + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedExtraUserObjectPermissionList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/roles/: + get: + operationId: rbac_roles_list + description: Role viewset + parameters: + - in: query + name: group__name + schema: + type: string + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRoleList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + post: + operationId: rbac_roles_create + description: Role viewset + tags: + - rbac + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RoleRequest' + required: true + security: + - authentik: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Role' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/roles/{uuid}/: + get: + operationId: rbac_roles_retrieve + description: Role viewset + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Role. + required: true + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Role' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + put: + operationId: rbac_roles_update + description: Role viewset + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Role. + required: true + tags: + - rbac + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RoleRequest' + required: true + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Role' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + patch: + operationId: rbac_roles_partial_update + description: Role viewset + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Role. + required: true + tags: + - rbac + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedRoleRequest' + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Role' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + delete: + operationId: rbac_roles_destroy + description: Role viewset + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Role. + required: true + tags: + - rbac + security: + - authentik: [] + responses: + '204': + description: No response body + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /rbac/roles/{uuid}/used_by/: + get: + operationId: rbac_roles_used_by_list + description: Get a list of all objects that use this object + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Role. + required: true + tags: + - rbac + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/UsedBy' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' /root/config/: get: operationId: root_config_retrieve @@ -22847,6 +23909,10 @@ paths: operationId: stages_deny_list description: DenyStage Viewset parameters: + - in: query + name: deny_message + schema: + type: string - in: query name: name schema: @@ -26725,6 +27791,7 @@ components: - authentik.providers.radius - authentik.providers.saml - authentik.providers.scim + - authentik.rbac - authentik.recovery - authentik.sources.ldap - authentik.sources.oauth @@ -26775,6 +27842,7 @@ components: * `authentik.providers.radius` - authentik Providers.Radius * `authentik.providers.saml` - authentik Providers.SAML * `authentik.providers.scim` - authentik Providers.SCIM + * `authentik.rbac` - authentik RBAC * `authentik.recovery` - authentik Recovery * `authentik.sources.ldap` - authentik Sources.LDAP * `authentik.sources.oauth` - authentik Sources.OAuth @@ -28495,11 +29563,11 @@ components: permissions: type: array items: - $ref: '#/components/schemas/Permission' + $ref: '#/components/schemas/ConsentPermission' additional_permissions: type: array items: - $ref: '#/components/schemas/Permission' + $ref: '#/components/schemas/ConsentPermission' token: type: string required: @@ -28522,6 +29590,17 @@ components: minLength: 1 required: - token + ConsentPermission: + type: object + description: Permission used for consent + properties: + name: + type: string + id: + type: string + required: + - id + - name ConsentStage: type: object description: ConsentStage Serializer @@ -28711,6 +29790,8 @@ components: type: array items: $ref: '#/components/schemas/FlowSet' + deny_message: + type: string required: - component - meta_model_name @@ -28729,6 +29810,8 @@ components: type: array items: $ref: '#/components/schemas/FlowSetRequest' + deny_message: + type: string required: - name Device: @@ -29510,6 +30593,7 @@ components: * `authentik.providers.radius` - authentik Providers.Radius * `authentik.providers.saml` - authentik Providers.SAML * `authentik.providers.scim` - authentik Providers.SCIM + * `authentik.rbac` - authentik RBAC * `authentik.recovery` - authentik Recovery * `authentik.sources.ldap` - authentik Sources.LDAP * `authentik.sources.oauth` - authentik Sources.OAuth @@ -29556,7 +30640,7 @@ components: * `authentik_flows.flowstagebinding` - Flow Stage Binding * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection - * `authentik_outposts.outpost` - outpost + * `authentik_outposts.outpost` - Outpost * `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy @@ -29577,6 +30661,7 @@ components: * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping * `authentik_providers_scim.scimprovider` - SCIM Provider * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_rbac.role` - Role * `authentik_sources_ldap.ldapsource` - LDAP Source * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping * `authentik_sources_oauth.oauthsource` - OAuth Source @@ -29590,9 +30675,9 @@ components: * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage * `authentik_stages_authenticator_sms.smsdevice` - SMS Device * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage - * `authentik_stages_authenticator_static.staticdevice` - Static device + * `authentik_stages_authenticator_static.staticdevice` - Static Device * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage - * `authentik_stages_authenticator_totp.totpdevice` - TOTP device + * `authentik_stages_authenticator_totp.totpdevice` - TOTP Device * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device @@ -29614,10 +30699,11 @@ components: * `authentik_stages_user_write.userwritestage` - User Write Stage * `authentik_tenants.tenant` - Tenant * `authentik_blueprints.blueprintinstance` - Blueprint Instance - * `authentik_core.group` - group + * `authentik_core.group` - Group * `authentik_core.user` - User * `authentik_core.application` - Application * `authentik_core.token` - Token + * `authentik_enterprise.license` - License required: - bound_to - component @@ -29703,6 +30789,7 @@ components: * `authentik.providers.radius` - authentik Providers.Radius * `authentik.providers.saml` - authentik Providers.SAML * `authentik.providers.scim` - authentik Providers.SCIM + * `authentik.rbac` - authentik RBAC * `authentik.recovery` - authentik Recovery * `authentik.sources.ldap` - authentik Sources.LDAP * `authentik.sources.oauth` - authentik Sources.OAuth @@ -29749,7 +30836,7 @@ components: * `authentik_flows.flowstagebinding` - Flow Stage Binding * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection - * `authentik_outposts.outpost` - outpost + * `authentik_outposts.outpost` - Outpost * `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy @@ -29770,6 +30857,7 @@ components: * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping * `authentik_providers_scim.scimprovider` - SCIM Provider * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_rbac.role` - Role * `authentik_sources_ldap.ldapsource` - LDAP Source * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping * `authentik_sources_oauth.oauthsource` - OAuth Source @@ -29783,9 +30871,9 @@ components: * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage * `authentik_stages_authenticator_sms.smsdevice` - SMS Device * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage - * `authentik_stages_authenticator_static.staticdevice` - Static device + * `authentik_stages_authenticator_static.staticdevice` - Static Device * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage - * `authentik_stages_authenticator_totp.totpdevice` - TOTP device + * `authentik_stages_authenticator_totp.totpdevice` - TOTP Device * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device @@ -29807,10 +30895,11 @@ components: * `authentik_stages_user_write.userwritestage` - User Write Stage * `authentik_tenants.tenant` - Tenant * `authentik_blueprints.blueprintinstance` - Blueprint Instance - * `authentik_core.group` - group + * `authentik_core.group` - Group * `authentik_core.user` - User * `authentik_core.application` - Application * `authentik_core.token` - Token + * `authentik_enterprise.license` - License required: - name EventRequest: @@ -29948,6 +31037,106 @@ components: required: - expression - name + ExtraRoleObjectPermission: + type: object + description: User permission with additional object-related data + properties: + id: + type: integer + readOnly: true + codename: + type: string + readOnly: true + model: + type: string + title: Python model class name + readOnly: true + app_label: + type: string + readOnly: true + object_pk: + type: string + title: Object ID + readOnly: true + name: + type: string + readOnly: true + app_label_verbose: + type: string + description: Get app label from permission's model + readOnly: true + model_verbose: + type: string + description: Get model label from permission's model + readOnly: true + object_description: + type: string + nullable: true + description: |- + Get model description from attached model. This operation takes at least + one additional query, and the description is only shown if the user/role has the + view_ permission on the object + readOnly: true + required: + - app_label + - app_label_verbose + - codename + - id + - model + - model_verbose + - name + - object_description + - object_pk + ExtraUserObjectPermission: + type: object + description: User permission with additional object-related data + properties: + id: + type: integer + readOnly: true + codename: + type: string + readOnly: true + model: + type: string + title: Python model class name + readOnly: true + app_label: + type: string + readOnly: true + object_pk: + type: string + title: Object ID + readOnly: true + name: + type: string + readOnly: true + app_label_verbose: + type: string + description: Get app label from permission's model + readOnly: true + model_verbose: + type: string + description: Get model label from permission's model + readOnly: true + object_description: + type: string + nullable: true + description: |- + Get model description from attached model. This operation takes at least + one additional query, and the description is only shown if the user/role has the + view_ permission on the object + readOnly: true + required: + - app_label + - app_label_verbose + - codename + - id + - model + - model_verbose + - name + - object_description + - object_pk FilePathRequest: type: object description: Serializer to upload file @@ -30548,19 +31737,30 @@ components: type: array items: type: integer - attributes: - type: object - additionalProperties: {} users_obj: type: array items: $ref: '#/components/schemas/GroupMember' readOnly: true + attributes: + type: object + additionalProperties: {} + roles: + type: array + items: + type: string + format: uuid + roles_obj: + type: array + items: + $ref: '#/components/schemas/Role' + readOnly: true required: - name - num_pk - parent_name - pk + - roles_obj - users_obj GroupMember: type: object @@ -30661,6 +31861,11 @@ components: attributes: type: object additionalProperties: {} + roles: + type: array + items: + type: string + format: uuid required: - name IdentificationChallenge: @@ -31930,6 +33135,7 @@ components: - authentik_providers_saml.samlpropertymapping - authentik_providers_scim.scimprovider - authentik_providers_scim.scimmapping + - authentik_rbac.role - authentik_sources_ldap.ldapsource - authentik_sources_ldap.ldappropertymapping - authentik_sources_oauth.oauthsource @@ -31971,6 +33177,7 @@ components: - authentik_core.user - authentik_core.application - authentik_core.token + - authentik_enterprise.license type: string description: |- * `authentik_crypto.certificatekeypair` - Certificate-Key Pair @@ -31983,7 +33190,7 @@ components: * `authentik_flows.flowstagebinding` - Flow Stage Binding * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection - * `authentik_outposts.outpost` - outpost + * `authentik_outposts.outpost` - Outpost * `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy @@ -32004,6 +33211,7 @@ components: * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping * `authentik_providers_scim.scimprovider` - SCIM Provider * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_rbac.role` - Role * `authentik_sources_ldap.ldapsource` - LDAP Source * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping * `authentik_sources_oauth.oauthsource` - OAuth Source @@ -32017,9 +33225,9 @@ components: * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage * `authentik_stages_authenticator_sms.smsdevice` - SMS Device * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage - * `authentik_stages_authenticator_static.staticdevice` - Static device + * `authentik_stages_authenticator_static.staticdevice` - Static Device * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage - * `authentik_stages_authenticator_totp.totpdevice` - TOTP device + * `authentik_stages_authenticator_totp.totpdevice` - TOTP Device * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device @@ -32041,10 +33249,11 @@ components: * `authentik_stages_user_write.userwritestage` - User Write Stage * `authentik_tenants.tenant` - Tenant * `authentik_blueprints.blueprintinstance` - Blueprint Instance - * `authentik_core.group` - group + * `authentik_core.group` - Group * `authentik_core.user` - User * `authentik_core.application` - Application * `authentik_core.token` - Token + * `authentik_enterprise.license` - License NameIdPolicyEnum: enum: - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress @@ -33292,6 +34501,30 @@ components: required: - pagination - results + PaginatedExtraRoleObjectPermissionList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/ExtraRoleObjectPermission' + required: + - pagination + - results + PaginatedExtraUserObjectPermissionList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/ExtraUserObjectPermission' + required: + - pagination + - results PaginatedFlowList: type: object properties: @@ -33556,6 +34789,18 @@ components: required: - pagination - results + PaginatedPermissionList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/Permission' + required: + - pagination + - results PaginatedPlexSourceConnectionList: type: object properties: @@ -33724,6 +34969,30 @@ components: required: - pagination - results + PaginatedRoleAssignedObjectPermissionList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/RoleAssignedObjectPermission' + required: + - pagination + - results + PaginatedRoleList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/Role' + required: + - pagination + - results PaginatedSAMLPropertyMappingList: type: object properties: @@ -33904,6 +35173,18 @@ components: required: - pagination - results + PaginatedUserAssignedObjectPermissionList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/UserAssignedObjectPermission' + required: + - pagination + - results PaginatedUserConsentList: type: object properties: @@ -34724,6 +36005,8 @@ components: type: array items: $ref: '#/components/schemas/FlowSetRequest' + deny_message: + type: string PatchedDockerServiceConnectionRequest: type: object description: DockerServiceConnection Serializer @@ -34927,6 +36210,7 @@ components: * `authentik.providers.radius` - authentik Providers.Radius * `authentik.providers.saml` - authentik Providers.SAML * `authentik.providers.scim` - authentik Providers.SCIM + * `authentik.rbac` - authentik RBAC * `authentik.recovery` - authentik Recovery * `authentik.sources.ldap` - authentik Sources.LDAP * `authentik.sources.oauth` - authentik Sources.OAuth @@ -34973,7 +36257,7 @@ components: * `authentik_flows.flowstagebinding` - Flow Stage Binding * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection - * `authentik_outposts.outpost` - outpost + * `authentik_outposts.outpost` - Outpost * `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy @@ -34994,6 +36278,7 @@ components: * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping * `authentik_providers_scim.scimprovider` - SCIM Provider * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_rbac.role` - Role * `authentik_sources_ldap.ldapsource` - LDAP Source * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping * `authentik_sources_oauth.oauthsource` - OAuth Source @@ -35007,9 +36292,9 @@ components: * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage * `authentik_stages_authenticator_sms.smsdevice` - SMS Device * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage - * `authentik_stages_authenticator_static.staticdevice` - Static device + * `authentik_stages_authenticator_static.staticdevice` - Static Device * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage - * `authentik_stages_authenticator_totp.totpdevice` - TOTP device + * `authentik_stages_authenticator_totp.totpdevice` - TOTP Device * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device @@ -35031,10 +36316,11 @@ components: * `authentik_stages_user_write.userwritestage` - User Write Stage * `authentik_tenants.tenant` - Tenant * `authentik_blueprints.blueprintinstance` - Blueprint Instance - * `authentik_core.group` - group + * `authentik_core.group` - Group * `authentik_core.user` - User * `authentik_core.application` - Application * `authentik_core.token` - Token + * `authentik_enterprise.license` - License PatchedEventRequest: type: object description: Event Serializer @@ -35184,6 +36470,11 @@ components: attributes: type: object additionalProperties: {} + roles: + type: array + items: + type: string + format: uuid PatchedIdentificationStageRequest: type: object description: IdentificationStage Serializer @@ -35891,6 +37182,20 @@ components: minimum: -2147483648 description: How many attempts a user has before the flow is canceled. To lock the user out, use a reputation policy and a user_write stage. + PatchedPermissionAssignRequest: + type: object + description: Request to assign a new permission + properties: + permissions: + type: array + items: + type: string + minLength: 1 + model: + $ref: '#/components/schemas/ModelEnum' + object_pk: + type: string + minLength: 1 PatchedPlexSourceConnectionRequest: type: object description: Plex Source connection Serializer @@ -36179,6 +37484,13 @@ components: type: string minLength: 1 description: Shared secret between clients and server to hash packets. + mfa_support: + type: boolean + description: When enabled, code-based multi-factor authentication can be + used by appending a semicolon and the TOTP code to the password. This + should only be enabled if all users that will bind to this provider have + a TOTP device configured, as otherwise a password may incorrectly be rejected + if it contains a semicolon. PatchedReputationPolicyRequest: type: object description: Reputation Policy Serializer @@ -36198,6 +37510,14 @@ components: type: integer maximum: 2147483647 minimum: -2147483648 + PatchedRoleRequest: + type: object + description: Role serializer + properties: + name: + type: string + minLength: 1 + maxLength: 150 PatchedSAMLPropertyMappingRequest: type: object description: SAMLPropertyMapping Serializer @@ -36744,15 +38064,56 @@ components: maxLength: 200 Permission: type: object - description: Permission used for consent + description: Global permission properties: + id: + type: integer + readOnly: true name: type: string - id: + maxLength: 255 + codename: type: string + maxLength: 100 + model: + type: string + title: Python model class name + readOnly: true + app_label: + type: string + readOnly: true + app_label_verbose: + type: string + description: Human-readable app label + readOnly: true + model_verbose: + type: string + description: Human-readable model name + readOnly: true required: + - app_label + - app_label_verbose + - codename - id + - model + - model_verbose - name + PermissionAssignRequest: + type: object + description: Request to assign a new permission + properties: + permissions: + type: array + items: + type: string + minLength: 1 + model: + $ref: '#/components/schemas/ModelEnum' + object_pk: + type: string + minLength: 1 + required: + - permissions PlexAuthenticationChallenge: type: object description: Challenge shown to the user in identification stage @@ -38025,6 +39386,13 @@ components: shared_secret: type: string description: Shared secret between clients and server to hash packets. + mfa_support: + type: boolean + description: When enabled, code-based multi-factor authentication can be + used by appending a semicolon and the TOTP code to the password. This + should only be enabled if all users that will bind to this provider have + a TOTP device configured, as otherwise a password may incorrectly be rejected + if it contains a semicolon. required: - application_slug - auth_flow_slug @@ -38100,6 +39468,13 @@ components: items: type: string readOnly: true + mfa_support: + type: boolean + description: When enabled, code-based multi-factor authentication can be + used by appending a semicolon and the TOTP code to the password. This + should only be enabled if all users that will bind to this provider have + a TOTP device configured, as otherwise a password may incorrectly be rejected + if it contains a semicolon. required: - assigned_application_name - assigned_application_slug @@ -38145,6 +39520,13 @@ components: type: string minLength: 1 description: Shared secret between clients and server to hash packets. + mfa_support: + type: boolean + description: When enabled, code-based multi-factor authentication can be + used by appending a semicolon and the TOTP code to the password. This + should only be enabled if all users that will bind to this provider have + a TOTP device configured, as otherwise a password may incorrectly be rejected + if it contains a semicolon. required: - authorization_flow - name @@ -38280,6 +39662,80 @@ components: * `discouraged` - Discouraged * `preferred` - Preferred * `required` - Required + Role: + type: object + description: Role serializer + properties: + pk: + type: string + format: uuid + readOnly: true + title: Uuid + name: + type: string + maxLength: 150 + required: + - name + - pk + RoleAssignedObjectPermission: + type: object + description: Roles assigned object permission serializer + properties: + role_pk: + type: string + readOnly: true + name: + type: string + readOnly: true + permissions: + type: array + items: + $ref: '#/components/schemas/RoleObjectPermission' + required: + - name + - permissions + - role_pk + RoleObjectPermission: + type: object + description: Role-bound object level permission + properties: + id: + type: integer + readOnly: true + codename: + type: string + readOnly: true + model: + type: string + title: Python model class name + readOnly: true + app_label: + type: string + readOnly: true + object_pk: + type: string + title: Object ID + readOnly: true + name: + type: string + readOnly: true + required: + - app_label + - codename + - id + - model + - name + - object_pk + RoleRequest: + type: object + description: Role serializer + properties: + name: + type: string + minLength: 1 + maxLength: 150 + required: + - name SAMLMetadata: type: object description: SAML Provider Metadata serializer @@ -40176,6 +41632,56 @@ components: type: integer required: - pk + UserAssignedObjectPermission: + type: object + description: Users assigned object permission serializer + properties: + pk: + type: integer + readOnly: true + title: ID + username: + type: string + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + pattern: ^[\w.@+-]+$ + maxLength: 150 + name: + type: string + description: User's display name. + is_active: + type: boolean + title: Active + description: Designates whether this user should be treated as active. Unselect + this instead of deleting accounts. + last_login: + type: string + format: date-time + nullable: true + email: + type: string + format: email + title: Email address + maxLength: 254 + attributes: + type: object + additionalProperties: {} + uid: + type: string + readOnly: true + permissions: + type: array + items: + $ref: '#/components/schemas/UserObjectPermission' + is_superuser: + type: boolean + required: + - is_superuser + - name + - permissions + - pk + - uid + - username UserConsent: type: object description: UserConsent Serializer @@ -40564,6 +42070,37 @@ components: required: - identifier - user + UserObjectPermission: + type: object + description: User-bound object level permission + properties: + id: + type: integer + readOnly: true + codename: + type: string + readOnly: true + model: + type: string + title: Python model class name + readOnly: true + app_label: + type: string + readOnly: true + object_pk: + type: string + title: Object ID + readOnly: true + name: + type: string + readOnly: true + required: + - app_label + - codename + - id + - model + - name + - object_pk UserPasswordSetRequest: type: object properties: @@ -40705,6 +42242,12 @@ components: readOnly: true type: $ref: '#/components/schemas/UserTypeEnum' + system_permissions: + type: array + items: + type: string + description: Get all system permissions assigned to the user + readOnly: true required: - avatar - groups @@ -40713,6 +42256,7 @@ components: - name - pk - settings + - system_permissions - uid - username UserSelfGroups: diff --git a/tests/wdio/.eslintrc.json b/tests/wdio/.eslintrc.json index 68900693d..d4095a2a3 100644 --- a/tests/wdio/.eslintrc.json +++ b/tests/wdio/.eslintrc.json @@ -15,6 +15,15 @@ "linebreak-style": ["error", "unix"], "quotes": ["error", "double", { "avoidEscape": true }], "semi": ["error", "always"], - "@typescript-eslint/ban-ts-comment": "off" + "@typescript-eslint/ban-ts-comment": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] } } diff --git a/tests/wdio/Makefile b/tests/wdio/Makefile index f5bdb0696..f45847bbe 100644 --- a/tests/wdio/Makefile +++ b/tests/wdio/Makefile @@ -36,3 +36,6 @@ test-good-login: node_modules admin-user ## Test that we can log into the serve test-bad-login: node_modules admin-user ## Test that bad usernames and passwords create appropriate error messages $(SPEC)/bad-logins.ts + +test-application-wizard: node_modules admin-user ## Test that the application wizard works as expected + $(SPEC)/new-application-by-wizard.ts diff --git a/tests/wdio/package-lock.json b/tests/wdio/package-lock.json index d594c937c..8f1762483 100644 --- a/tests/wdio/package-lock.json +++ b/tests/wdio/package-lock.json @@ -7,12 +7,12 @@ "name": "@goauthentik/web-tests", "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.2.0", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", - "@wdio/cli": "^8.17.0", - "@wdio/local-runner": "^8.17.0", - "@wdio/mocha-framework": "^8.17.0", - "@wdio/spec-reporter": "^8.17.0", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "@wdio/cli": "^8.19.0", + "@wdio/local-runner": "^8.19.0", + "@wdio/mocha-framework": "^8.19.0", + "@wdio/spec-reporter": "^8.19.0", "eslint": "^8.51.0", "eslint-config-google": "^0.14.0", "eslint-plugin-sonarjs": "^0.21.0", @@ -878,16 +878,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", - "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", + "integrity": "sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/type-utils": "6.7.5", - "@typescript-eslint/utils": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/type-utils": "6.8.0", + "@typescript-eslint/utils": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -913,15 +913,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", - "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", + "integrity": "sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4" }, "engines": { @@ -941,13 +941,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", - "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz", + "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5" + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -958,13 +958,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", - "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz", + "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/utils": "6.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -985,9 +985,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", - "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz", + "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -998,13 +998,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", - "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz", + "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1025,17 +1025,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", - "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz", + "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", "semver": "^7.5.4" }, "engines": { @@ -1050,12 +1050,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", - "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz", + "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/types": "6.8.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1067,18 +1067,18 @@ } }, "node_modules/@wdio/cli": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.17.0.tgz", - "integrity": "sha512-RF7QMY4K9aS6kQipHcmPPtuo5VZd8UNOqt2dw97b2LKxK0niDOAwQNQSB5lzhCOJ6sRVAIlX/DVo23zhGbldvA==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.19.0.tgz", + "integrity": "sha512-OLYIyVx1B6DK9xgiqFTNSZkDSug5so4MylwI7whJ1IdQmmwsRVS3rLuac0qYPr+z7G9QnZrqSrqCNkfwJ98V+Q==", "dev": true, "dependencies": { "@types/node": "^20.1.1", - "@wdio/config": "8.17.0", - "@wdio/globals": "8.17.0", + "@wdio/config": "8.19.0", + "@wdio/globals": "8.19.0", "@wdio/logger": "8.16.17", - "@wdio/protocols": "8.16.5", - "@wdio/types": "8.17.0", - "@wdio/utils": "8.17.0", + "@wdio/protocols": "8.18.0", + "@wdio/types": "8.19.0", + "@wdio/utils": "8.19.0", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", @@ -1093,7 +1093,7 @@ "lodash.union": "^4.6.0", "read-pkg-up": "10.1.0", "recursive-readdir": "^2.2.3", - "webdriverio": "8.17.0", + "webdriverio": "8.19.0", "yargs": "^17.7.2", "yarn-install": "^1.0.0" }, @@ -1117,14 +1117,14 @@ } }, "node_modules/@wdio/config": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.17.0.tgz", - "integrity": "sha512-6qUgE99D8XSKSDdwLrpeEatJ133Ce0UPrIyTNdsIFOQ7vSmwBif+vmFDSa7mCt1+ay2hLYglEwVJ1r+48Ke/pw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.19.0.tgz", + "integrity": "sha512-BFsLLoOD8kE1qGtAaY22N1c/GPOJbToQgD56ZZCS1wbLwX4EfZk6QIsqV2XcyEGzTZjge2GCkZEbUMHPLrMXvQ==", "dev": true, "dependencies": { "@wdio/logger": "8.16.17", - "@wdio/types": "8.17.0", - "@wdio/utils": "8.17.0", + "@wdio/types": "8.19.0", + "@wdio/utils": "8.19.0", "decamelize": "^6.0.0", "deepmerge-ts": "^5.0.0", "glob": "^10.2.2", @@ -1136,29 +1136,29 @@ } }, "node_modules/@wdio/globals": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.17.0.tgz", - "integrity": "sha512-SWI1faPNYgZnPwS2TZF+/Vpg2wxB8Yx0nHv/t3JvZ/QOZs/NpWJ9oXNjCDuktljNXaYhUOgYsx1e5DHHR6VTuQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.19.0.tgz", + "integrity": "sha512-upmDIy6HWB2mU8WC38OTnuo8984GVypzy2qUpGRcynXBuQLVLijxQLX+gGi4I5hBJu3K6eFhnDkhI0oXoV92gw==", "dev": true, "engines": { "node": "^16.13 || >=18" }, "optionalDependencies": { "expect-webdriverio": "^4.2.5", - "webdriverio": "8.17.0" + "webdriverio": "8.19.0" } }, "node_modules/@wdio/local-runner": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.17.0.tgz", - "integrity": "sha512-hYubvTs80U2h9s4mtd7+znRiQwLnn2duwdYW0L+pRkt3yDmaA76R/a//GrpIFqn9j04463S9xv4pOGOat10fPA==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.19.0.tgz", + "integrity": "sha512-WX9rN6WqqDipJhDQJV2+nDN4FY9sz3wmDdoi2X+W7KvnU8Hev2BexEb8PpzqVwt2aVL0gVwLb4cUdU7gWITHyg==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@wdio/logger": "8.16.17", "@wdio/repl": "8.10.1", - "@wdio/runner": "8.17.0", - "@wdio/types": "8.17.0", + "@wdio/runner": "8.19.0", + "@wdio/types": "8.19.0", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" @@ -1195,16 +1195,16 @@ } }, "node_modules/@wdio/mocha-framework": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.17.0.tgz", - "integrity": "sha512-jHNsPib3sddudsULxxVJi/M4k+A+YfrkZr1covvEciLUF4Myr8O1D6GOLPT8HkqY3XMU6PKjs1Xz1rkZokmuAw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.19.0.tgz", + "integrity": "sha512-AtyTRnnRR/RM7yHQNa2oIwcGNYkd0boDxrtoSwl+qnUEN1lRjM3t+C7hYOTtv1J9PCGNtMK8pM4SD/IcujZHmA==", "dev": true, "dependencies": { "@types/mocha": "^10.0.0", "@types/node": "^20.1.0", "@wdio/logger": "8.16.17", - "@wdio/types": "8.17.0", - "@wdio/utils": "8.17.0", + "@wdio/types": "8.19.0", + "@wdio/utils": "8.19.0", "mocha": "^10.0.0" }, "engines": { @@ -1212,9 +1212,9 @@ } }, "node_modules/@wdio/protocols": { - "version": "8.16.5", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.16.5.tgz", - "integrity": "sha512-u9I57hIqmcOgrDH327ZCc2GTXv2YFN5bg6UaA3OUoJU7eJgGYHFB6RrjiNjLXer68iIx07wwVM70V/1xzijd3Q==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.18.0.tgz", + "integrity": "sha512-TABA0mksHvu5tE8qNYYDR0fDyo90NCANeghbGAtsI8TUsJzgH0dwpos3WSSiB97J9HRSZuWIMa7YuABEkBIjWQ==", "dev": true }, "node_modules/@wdio/repl": { @@ -1230,14 +1230,14 @@ } }, "node_modules/@wdio/reporter": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.17.0.tgz", - "integrity": "sha512-U4GMgHjH+mgtwKUXHk1fYsIwET0NUvC5efWN8z/pSyzc93iHHzl1cY4FSAjND6KTnPFpxJB+3EamgQRpC2P92Q==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.19.0.tgz", + "integrity": "sha512-fRECsIIryM+2xnjcYDXx1j3c8GkPtBi5/dqowYPKW1bdm/SMCOBZviZVbihpw7zSgo+cGAMbFXEO9HDVcMqSjg==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@wdio/logger": "8.16.17", - "@wdio/types": "8.17.0", + "@wdio/types": "8.19.0", "diff": "^5.0.0", "object-inspect": "^1.12.0" }, @@ -1246,35 +1246,35 @@ } }, "node_modules/@wdio/runner": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.17.0.tgz", - "integrity": "sha512-icWRiCytpIlrJGq2CUUS4QfrFjiiX2pDoBGH1hr7L8XDorQ7niyTQWYPj7DoDuDy42poWuCjsCxZCBgb5YHw1Q==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.19.0.tgz", + "integrity": "sha512-YZXVzg9Q4S/vRFfzzprQYgq9scPZohpSUhJxOjS4xYY/yorMRYTR/zih1aQCSQVgQ/9PadRqCs2a9fElsLdOMA==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.17.0", - "@wdio/globals": "8.17.0", + "@wdio/config": "8.19.0", + "@wdio/globals": "8.19.0", "@wdio/logger": "8.16.17", - "@wdio/types": "8.17.0", - "@wdio/utils": "8.17.0", + "@wdio/types": "8.19.0", + "@wdio/utils": "8.19.0", "deepmerge-ts": "^5.0.0", "expect-webdriverio": "^4.2.5", "gaze": "^1.1.2", - "webdriver": "8.17.0", - "webdriverio": "8.17.0" + "webdriver": "8.19.0", + "webdriverio": "8.19.0" }, "engines": { "node": "^16.13 || >=18" } }, "node_modules/@wdio/spec-reporter": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.17.0.tgz", - "integrity": "sha512-qefsqN71S0Imbcdq7mWqVVij6qbLw8Mx55tUsr+ImPhDDQWbh+XPgP0tsTDbdLmPez7V2Ui0wovHA2WYbif3GQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.19.0.tgz", + "integrity": "sha512-3QV1AEJM/Utr8EitSpQI3JfNhGYryLVaQpIy4u/LxDj2NjHAebhs4eoEwsc1nUrtl0YXt5y6L5LopmxTpILDMw==", "dev": true, "dependencies": { - "@wdio/reporter": "8.17.0", - "@wdio/types": "8.17.0", + "@wdio/reporter": "8.19.0", + "@wdio/types": "8.19.0", "chalk": "^5.1.2", "easy-table": "^1.2.0", "pretty-ms": "^7.0.0" @@ -1296,9 +1296,9 @@ } }, "node_modules/@wdio/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.17.0.tgz", - "integrity": "sha512-OkIpr5iHcwFXQpr4csXsiQ/WelX+Dhz/A8STFzoDQFYxMlR3nzm/S+Q1P4UoJfyhrNWlsFpLhShGK1cn+XUE5Q==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.19.0.tgz", + "integrity": "sha512-L2DCjRkOYEkEcZewBMCCLrsFJIYzo+kUcoV8iX3oDH711pxdC6hJIK8r7EeeLDPklNHqnxGniVY/+04lpOoqmg==", "dev": true, "dependencies": { "@types/node": "^20.1.0" @@ -1308,14 +1308,14 @@ } }, "node_modules/@wdio/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-WkXY+kSFOi/7tztB1uWVRfu6E/4TIEBYni+qCYTkaPI5903EDratkeakINuu63xL7WtYv9adt7ndtDVcsi1KTg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.19.0.tgz", + "integrity": "sha512-Pwpoc0yqFMtVVv7Wp5zAJKO8qNRcbVHRGOdc62UFpXD09+kvnwhsgCJcQPPQndCebbDgvhFok3rBcgYrjEz5rQ==", "dev": true, "dependencies": { "@puppeteer/browsers": "^1.6.0", "@wdio/logger": "8.16.17", - "@wdio/types": "8.17.0", + "@wdio/types": "8.19.0", "decamelize": "^6.0.0", "deepmerge-ts": "^5.1.0", "edgedriver": "^5.3.5", @@ -2595,9 +2595,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1206220", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1206220.tgz", - "integrity": "sha512-zTcXveZkrQdpBwZzAd6spwu+WZet0hU+m/hAm7j61PDUQgG42YkMMdbFYqbDrxIiMTEgJInn70ck1Jl10RQ1aQ==", + "version": "0.0.1209236", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1209236.tgz", + "integrity": "sha512-z4eehc+fhmptqhxwreLcg9iydszZGU4Q5FzaaElXVGp3KyfXbjtXeUCmo4l8FxBJbyXtCz4VRIJsGW2ekApyUQ==", "dev": true }, "node_modules/diff": { @@ -8693,18 +8693,18 @@ } }, "node_modules/webdriver": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.17.0.tgz", - "integrity": "sha512-YxAOPJx4dxVOsN2A7XpFu1IzA12M3yO82oDCjauyPGJ7+TQgXGVqEuk0wtNzOn8Ok8uq7sPFkne5ASQBsH6cWg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.19.0.tgz", + "integrity": "sha512-7LLDiiAnhUE4AsQjbpql7bPxVYGg7fOgrncebRSnwerPeFDnjMxV+MNs42bIpQFscncYAndKZR5t1DP1vC240A==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.17.0", + "@wdio/config": "8.19.0", "@wdio/logger": "8.16.17", - "@wdio/protocols": "8.16.5", - "@wdio/types": "8.17.0", - "@wdio/utils": "8.17.0", + "@wdio/protocols": "8.18.0", + "@wdio/types": "8.19.0", + "@wdio/utils": "8.19.0", "deepmerge-ts": "^5.1.0", "got": "^ 12.6.1", "ky": "^0.33.0", @@ -8752,23 +8752,23 @@ } }, "node_modules/webdriverio": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.17.0.tgz", - "integrity": "sha512-nn4OzRAJOxWYRQDdQNM/XQ9QKYWfUjhirFwB3GeQ5vEcqzvJmU0U0DMwlMjDYi6O6RMvkJY384+GA/0Dfiq3vg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.19.0.tgz", + "integrity": "sha512-U+TDtkJBEkqD7Rux1EKsYTxmlwNt/l9WnDaO1oVQyazk5WRBGdtMxtF7Cm1AspSR0swsnx2NFBSte0IgI8mzUg==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.17.0", + "@wdio/config": "8.19.0", "@wdio/logger": "8.16.17", - "@wdio/protocols": "8.16.5", + "@wdio/protocols": "8.18.0", "@wdio/repl": "8.10.1", - "@wdio/types": "8.17.0", - "@wdio/utils": "8.17.0", + "@wdio/types": "8.19.0", + "@wdio/utils": "8.19.0", "archiver": "^6.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1206220", + "devtools-protocol": "^0.0.1209236", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^3.0.0", "is-plain-obj": "^4.1.0", @@ -8780,7 +8780,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.17.0" + "webdriver": "8.19.0" }, "engines": { "node": "^16.13 || >=18" diff --git a/tests/wdio/package.json b/tests/wdio/package.json index 8fbe2bf96..043c93758 100644 --- a/tests/wdio/package.json +++ b/tests/wdio/package.json @@ -4,12 +4,12 @@ "type": "module", "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.2.0", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", - "@wdio/cli": "^8.17.0", - "@wdio/local-runner": "^8.17.0", - "@wdio/mocha-framework": "^8.17.0", - "@wdio/spec-reporter": "^8.17.0", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "@wdio/cli": "^8.19.0", + "@wdio/local-runner": "^8.19.0", + "@wdio/mocha-framework": "^8.19.0", + "@wdio/spec-reporter": "^8.19.0", "eslint": "^8.51.0", "eslint-config-google": "^0.14.0", "eslint-plugin-sonarjs": "^0.21.0", diff --git a/tests/wdio/test/pageobjects/application-wizard.page.ts b/tests/wdio/test/pageobjects/application-wizard.page.ts new file mode 100644 index 000000000..2533a1abf --- /dev/null +++ b/tests/wdio/test/pageobjects/application-wizard.page.ts @@ -0,0 +1,75 @@ +import AdminPage from "./admin.page.js"; +import ApplicationForm from "./forms/application.form.js"; +import ForwardProxyForm from "./forms/forward-proxy.form.js"; +import LdapForm from "./forms/ldap.form.js"; +import OauthForm from "./forms/oauth.form.js"; +import RadiusForm from "./forms/radius.form.js"; +import SamlForm from "./forms/saml.form.js"; +import ScimForm from "./forms/scim.form.js"; +import TransparentProxyForm from "./forms/transparent-proxy.form.js"; +import { $ } from "@wdio/globals"; + +/** + * sub page containing specific selectors and methods for a specific page + */ + +class ApplicationWizardView extends AdminPage { + /** + * define selectors using getter methods + */ + + ldap = LdapForm; + oauth = OauthForm; + transparentProxy = TransparentProxyForm; + forwardProxy = ForwardProxyForm; + saml = SamlForm; + scim = ScimForm; + radius = RadiusForm; + app = ApplicationForm; + + get wizardTitle() { + return $(">>>ak-wizard-frame .pf-c-wizard__header h1.pf-c-title"); + } + + get providerList() { + return $(">>>ak-application-wizard-authentication-method-choice"); + } + + get nextButton() { + return $(">>>ak-wizard-frame footer button.pf-m-primary"); + } + + async getProviderType(type: string) { + return await this.providerList.$(`>>>input[value="${type}"]`); + } + + get successMessage() { + return $('>>>[data-commit-state="success"]'); + } +} + +type Pair = [string, string]; + +// Define a getter for each provider type in the radio button collection. + +const providerValues: Pair[] = [ + ["oauth2provider", "oauth2Provider"], + ["ldapprovider", "ldapProvider"], + ["proxyprovider-proxy", "proxyProviderProxy"], + ["proxyprovider-forwardsingle", "proxyProviderForwardsingle"], + ["radiusprovider", "radiusProvider"], + ["samlprovider", "samlProvider"], + ["scimprovider", "scimProvider"], +]; + +providerValues.forEach(([value, name]: Pair) => { + Object.defineProperties(ApplicationWizardView.prototype, { + [name]: { + get: function () { + return this.providerList.$(`>>>input[value="${value}"]`); + }, + }, + }); +}); + +export default new ApplicationWizardView(); diff --git a/tests/wdio/test/pageobjects/applications-list.page.ts b/tests/wdio/test/pageobjects/applications-list.page.ts new file mode 100644 index 000000000..0f3d93e03 --- /dev/null +++ b/tests/wdio/test/pageobjects/applications-list.page.ts @@ -0,0 +1,21 @@ +import AdminPage from "./admin.page.js"; +import { $ } from "@wdio/globals"; + +/** + * sub page containing specific selectors and methods for a specific page + */ +class ApplicationsListPage extends AdminPage { + /** + * define selectors using getter methods + */ + + get startWizardButton() { + return $('>>>ak-wizard-frame button[slot="trigger"]'); + } + + async open() { + return await super.open("if/admin/#/core/applications"); + } +} + +export default new ApplicationsListPage(); diff --git a/tests/wdio/test/pageobjects/forms/application.form.ts b/tests/wdio/test/pageobjects/forms/application.form.ts new file mode 100644 index 000000000..6f0d33217 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/application.form.ts @@ -0,0 +1,18 @@ +import Page from "../page.js"; +import { $ } from "@wdio/globals"; + +export class ApplicationForm extends Page { + get name() { + return $('>>>ak-form-element-horizontal input[name="name"]'); + } + + get uiSettings() { + return $('>>>ak-form-group button[aria-label="UI Settings"]'); + } + + get launchUrl() { + return $('>>>input[name="metaLaunchUrl"]'); + } +} + +export default new ApplicationForm(); diff --git a/tests/wdio/test/pageobjects/forms/forward-proxy.form.ts b/tests/wdio/test/pageobjects/forms/forward-proxy.form.ts new file mode 100644 index 000000000..9d9a8bb50 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/forward-proxy.form.ts @@ -0,0 +1,18 @@ +import Page from "../page.js"; +import { $ } from "@wdio/globals"; + +export class ForwardProxyForm extends Page { + async setAuthorizationFlow(selector: string) { + await this.searchSelect( + '>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', + "authorizationFlow", + `button*=${selector}`, + ); + } + + get externalHost() { + return $('>>>input[name="externalHost"]'); + } +} + +export default new ForwardProxyForm(); diff --git a/tests/wdio/test/pageobjects/forms/ldap.form.ts b/tests/wdio/test/pageobjects/forms/ldap.form.ts new file mode 100644 index 000000000..343fe583c --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/ldap.form.ts @@ -0,0 +1,13 @@ +import Page from "../page.js"; + +export class LdapForm extends Page { + async setBindFlow(selector: string) { + await this.searchSelect( + '>>>ak-tenanted-flow-search[name="authorizationFlow"] input[type="text"]', + "authorizationFlow", + `button*=${selector}`, + ); + } +} + +export default new LdapForm(); diff --git a/tests/wdio/test/pageobjects/forms/radius.form.ts b/tests/wdio/test/pageobjects/forms/radius.form.ts new file mode 100644 index 000000000..591459866 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/radius.form.ts @@ -0,0 +1,13 @@ +import Page from "../page.js"; + +export class RadiusForm extends Page { + async setAuthenticationFlow(selector: string) { + await this.searchSelect( + '>>>ak-tenanted-flow-search[name="authorizationFlow"] input[type="text"]', + "authorizationFlow", + `button*=${selector}`, + ); + } +} + +export default new RadiusForm(); diff --git a/tests/wdio/test/pageobjects/forms/saml.form.ts b/tests/wdio/test/pageobjects/forms/saml.form.ts new file mode 100644 index 000000000..4419e1cb5 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/saml.form.ts @@ -0,0 +1,18 @@ +import Page from "../page.js"; +import { $ } from "@wdio/globals"; + +export class SamlForm extends Page { + async setAuthorizationFlow(selector: string) { + await this.searchSelect( + '>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', + "authorizationFlow", + `button*=${selector}`, + ); + } + + get acsUrl() { + return $('>>>input[name="acsUrl"]'); + } +} + +export default new SamlForm(); diff --git a/tests/wdio/test/pageobjects/forms/scim.form.ts b/tests/wdio/test/pageobjects/forms/scim.form.ts new file mode 100644 index 000000000..41a11356c --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/scim.form.ts @@ -0,0 +1,13 @@ +import Page from "../page.js"; + +export class ScimForm extends Page { + get url() { + return $('>>>input[name="url"]'); + } + + get token() { + return $('>>>input[name="token"]'); + } +} + +export default new ScimForm(); diff --git a/tests/wdio/test/pageobjects/forms/transparent-proxy.form.ts b/tests/wdio/test/pageobjects/forms/transparent-proxy.form.ts new file mode 100644 index 000000000..505016946 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/transparent-proxy.form.ts @@ -0,0 +1,22 @@ +import Page from "../page.js"; +import { $ } from "@wdio/globals"; + +export class TransparentProxyForm extends Page { + async setAuthorizationFlow(selector: string) { + await this.searchSelect( + '>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', + "authorizationFlow", + `button*=${selector}`, + ); + } + + get externalHost() { + return $('>>>input[name="externalHost"]'); + } + + get internalHost() { + return $('>>>input[name="internalHost"]'); + } +} + +export default new TransparentProxyForm(); diff --git a/tests/wdio/test/specs/new-application-by-wizard.ts b/tests/wdio/test/specs/new-application-by-wizard.ts new file mode 100644 index 000000000..6e4965779 --- /dev/null +++ b/tests/wdio/test/specs/new-application-by-wizard.ts @@ -0,0 +1,167 @@ +import ApplicationWizardView from "../pageobjects/application-wizard.page.js"; +import ApplicationsListPage from "../pageobjects/applications-list.page.js"; +import { randomId } from "../utils/index.js"; +import { login } from "../utils/login.js"; +import { expect } from "@wdio/globals"; + +async function reachTheProvider(title: string) { + const newPrefix = randomId(); + + await ApplicationsListPage.logout(); + await login(); + await ApplicationsListPage.open(); + await expect(await ApplicationsListPage.pageHeader).toHaveText("Applications"); + + await ApplicationsListPage.startWizardButton.click(); + await ApplicationWizardView.wizardTitle.waitForDisplayed(); + await expect(await ApplicationWizardView.wizardTitle).toHaveText("New application"); + + await ApplicationWizardView.app.name.setValue(`${title} - ${newPrefix}`); + await ApplicationWizardView.app.uiSettings.scrollIntoView(); + await ApplicationWizardView.app.uiSettings.click(); + await ApplicationWizardView.app.launchUrl.scrollIntoView(); + await ApplicationWizardView.app.launchUrl.setValue("http://example.goauthentik.io"); + + await ApplicationWizardView.nextButton.click(); + return await ApplicationWizardView.pause(); +} + +async function getCommitMessage() { + await ApplicationWizardView.successMessage.waitForDisplayed(); + return await ApplicationWizardView.successMessage; +} + +const SUCCESS_MESSAGE = "Your application has been saved"; +const EXPLICIT_CONSENT = "default-provider-authorization-explicit-consent"; + +describe("Configure Applications with the Application Wizard", () => { + it("Should configure a simple LDAP Application", async () => { + await reachTheProvider("New LDAP Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.ldapProvider.scrollIntoView(); + await ApplicationWizardView.ldapProvider.click(); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.ldap.setBindFlow("default-authentication-flow"); + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE); + }); + + it("Should configure a simple Oauth2 Application", async () => { + await reachTheProvider("New Oauth2 Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.oauth2Provider.scrollIntoView(); + await ApplicationWizardView.oauth2Provider.click(); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.oauth.setAuthorizationFlow(EXPLICIT_CONSENT); + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE); + }); + + it("Should configure a simple SAML Application", async () => { + await reachTheProvider("New SAML Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.samlProvider.scrollIntoView(); + await ApplicationWizardView.samlProvider.click(); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.saml.setAuthorizationFlow(EXPLICIT_CONSENT); + await ApplicationWizardView.saml.acsUrl.setValue("http://example.com:8000/"); + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE); + }); + + it("Should configure a simple SCIM Application", async () => { + await reachTheProvider("New SCIM Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.scimProvider.scrollIntoView(); + await ApplicationWizardView.scimProvider.click(); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.scim.url.setValue("http://example.com:8000/"); + await ApplicationWizardView.scim.token.setValue("a-very-basic-token"); + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE); + }); + + it("Should configure a simple Radius Application", async () => { + await reachTheProvider("New Radius Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.radiusProvider.scrollIntoView(); + await ApplicationWizardView.radiusProvider.click(); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.radius.setAuthenticationFlow("default-authentication-flow"); + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE); + }); + + it("Should configure a simple Transparent Proxy Application", async () => { + await reachTheProvider("New Transparent Proxy Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.proxyProviderProxy.scrollIntoView(); + await ApplicationWizardView.proxyProviderProxy.click(); + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.transparentProxy.setAuthorizationFlow(EXPLICIT_CONSENT); + await ApplicationWizardView.transparentProxy.externalHost.setValue( + "http://external.example.com", + ); + await ApplicationWizardView.transparentProxy.internalHost.setValue( + "http://internal.example.com", + ); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE); + }); + + it("Should configure a simple Forward Proxy Application", async () => { + await reachTheProvider("New Forward Proxy Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.proxyProviderForwardsingle.scrollIntoView(); + await ApplicationWizardView.proxyProviderForwardsingle.click(); + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.forwardProxy.setAuthorizationFlow(EXPLICIT_CONSENT); + await ApplicationWizardView.forwardProxy.externalHost.setValue( + "http://external.example.com", + ); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE); + }); +}); diff --git a/tests/wdio/wdio.conf.ts b/tests/wdio/wdio.conf.ts index bb7420636..525cfb00d 100644 --- a/tests/wdio/wdio.conf.ts +++ b/tests/wdio/wdio.conf.ts @@ -86,7 +86,7 @@ export const config: Options.Testrunner = { // Define all options that are relevant for the WebdriverIO instance here // // Level of logging verbosity: trace | debug | info | warn | error | silent - logLevel: "info", + logLevel: "warn", // // Set specific log levels per logger // loggers: @@ -157,4 +157,149 @@ export const config: Options.Testrunner = { ui: "bdd", timeout: 60000, }, + // + // ===== + // Hooks + // ===== + // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance + // it and to build services around it. You can either apply a single function or an array of + // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got + // resolved to continue. + /** + * Gets executed once before all workers get launched. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + */ + // onPrepare: function (config, capabilities) { + // }, + /** + * Gets executed before a worker process is spawned and can be used to initialise specific service + * for that worker as well as modify runtime environments in an async fashion. + * @param {string} cid capability id (e.g 0-0) + * @param {object} caps object containing capabilities for session that will be spawn in the worker + * @param {object} specs specs to be run in the worker process + * @param {object} args object that will be merged with the main configuration once worker is initialized + * @param {object} execArgv list of string arguments passed to the worker process + */ + // onWorkerStart: function (cid, caps, specs, args, execArgv) { + // }, + /** + * Gets executed just after a worker process has exited. + * @param {string} cid capability id (e.g 0-0) + * @param {number} exitCode 0 - success, 1 - fail + * @param {object} specs specs to be run in the worker process + * @param {number} retries number of retries used + */ + // onWorkerEnd: function (cid, exitCode, specs, retries) { + // }, + /** + * Gets executed just before initialising the webdriver session and test framework. It allows you + * to manipulate configurations depending on the capability or spec. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + * @param {string} cid worker id (e.g. 0-0) + */ + // beforeSession: function (config, capabilities, specs, cid) { + // }, + /** + * Gets executed before test execution begins. At this point you can access to all global + * variables like `browser`. It is the perfect place to define custom commands. + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + * @param {object} browser instance of created browser/device session + */ + before: function (_capabilities, _specs) {}, + /** + * Runs before a WebdriverIO command gets executed. + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + */ + // beforeCommand: function (commandName, args) { + // }, + /** + * Hook that gets executed before the suite starts + * @param {object} suite suite details + */ + // beforeSuite: function (suite) { + // }, + /** + * Function to be executed before a test (in Mocha/Jasmine) starts. + */ + // beforeTest: function (test, context) { + // }, + /** + * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling + * beforeEach in Mocha) + */ + // beforeHook: function (test, context) { + // }, + /** + * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling + * afterEach in Mocha) + */ + // afterHook: function (test, context, { error, result, duration, passed, retries }) { + // }, + /** + * Function to be executed after a test (in Mocha/Jasmine only) + * @param {object} test test object + * @param {object} context scope object the test was executed with + * @param {Error} result.error error object in case the test fails, otherwise `undefined` + * @param {*} result.result return object of test function + * @param {number} result.duration duration of test + * @param {boolean} result.passed true if test has passed, otherwise false + * @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }` + */ + // afterTest: function(test, context, { error, result, duration, passed, retries }) { + // }, + + /** + * Hook that gets executed after the suite has ended + * @param {object} suite suite details + */ + // afterSuite: function (suite) { + // }, + /** + * Runs after a WebdriverIO command gets executed + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + * @param {number} result 0 - command success, 1 - command error + * @param {object} error error object if any + */ + // afterCommand: function (commandName, args, result, error) { + // }, + /** + * Gets executed after all tests are done. You still have access to all global variables from + * the test. + * @param {number} result 0 - test pass, 1 - test fail + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // after: function (result, capabilities, specs) { + // }, + /** + * Gets executed right after terminating the webdriver session. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // afterSession: function (config, capabilities, specs) { + // }, + /** + * Gets executed after all workers got shut down and the process is about to exit. An error + * thrown in the onComplete hook will result in the test run failing. + * @param {object} exitCode 0 - success, 1 - fail + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {} results object containing test results + */ + // onComplete: function(exitCode, config, capabilities, results) { + // }, + /** + * Gets executed when a refresh happens. + * @param {string} oldSessionId session ID of the old session + * @param {string} newSessionId session ID of the new session + */ + // onReload: function(oldSessionId, newSessionId) { + // } }; diff --git a/web/.gitignore b/web/.gitignore index 5fcf65536..f11bf366d 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -109,3 +109,5 @@ temp/ # End of https://www.gitignore.io/api/node api/** storybook-static/ +scripts/*.mjs +scripts/*.js diff --git a/web/package-lock.json b/web/package-lock.json index ec600c34c..e68389004 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -15,17 +15,17 @@ "@codemirror/lang-xml": "^6.0.2", "@codemirror/legacy-modes": "^6.3.3", "@codemirror/theme-one-dark": "^6.1.2", - "@formatjs/intl-listformat": "^7.4.2", + "@formatjs/intl-listformat": "^7.5.0", "@fortawesome/fontawesome-free": "^6.4.2", - "@goauthentik/api": "^2023.8.3-1696847703", + "@goauthentik/api": "^2023.8.3-1697651039", "@lit-labs/context": "^0.4.1", "@lit-labs/task": "^3.1.0", "@lit/localize": "^0.11.4", "@open-wc/lit-helpers": "^0.6.0", "@patternfly/elements": "^2.4.0", "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.73.0", - "@sentry/tracing": "^7.73.0", + "@sentry/browser": "^7.74.1", + "@sentry/tracing": "^7.74.1", "@webcomponents/webcomponentsjs": "^2.8.0", "base64-js": "^1.5.1", "chart.js": "^4.4.0", @@ -40,7 +40,7 @@ "rapidoc": "^9.3.4", "style-mod": "^4.1.0", "webcomponent-qr-code": "^1.2.0", - "yaml": "^2.3.2" + "yaml": "^2.3.3" }, "devDependencies": { "@babel/core": "^7.23.2", @@ -56,22 +56,22 @@ "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@lit/localize-tools": "^0.7.0", "@rollup/plugin-babel": "^6.0.4", - "@rollup/plugin-commonjs": "^25.0.5", + "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.3", + "@rollup/plugin-replace": "^5.0.4", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", - "@storybook/addon-essentials": "^7.4.6", - "@storybook/addon-links": "^7.4.6", + "@storybook/addon-essentials": "^7.5.0", + "@storybook/addon-links": "^7.5.0", "@storybook/blocks": "^7.1.1", - "@storybook/web-components": "^7.4.6", - "@storybook/web-components-vite": "^7.4.6", + "@storybook/web-components": "^7.5.0", + "@storybook/web-components-vite": "^7.5.0", "@trivago/prettier-plugin-sort-imports": "^4.2.0", - "@types/chart.js": "^2.9.38", - "@types/codemirror": "5.60.10", - "@types/grecaptcha": "^3.0.5", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", + "@types/chart.js": "^2.9.39", + "@types/codemirror": "5.60.12", + "@types/grecaptcha": "^3.0.6", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", "babel-plugin-macros": "^3.1.0", "babel-plugin-tsconfig-paths": "^1.0.3", "cross-env": "^7.0.3", @@ -84,14 +84,15 @@ "lit-analyzer": "^1.2.1", "npm-run-all": "^4.1.5", "prettier": "^3.0.3", - "pyright": "^1.1.331", + "pseudolocale": "^2.0.0", + "pyright": "^1.1.332", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.0.2", + "rollup": "^4.1.4", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-postcss-lit": "^2.1.0", - "storybook": "^7.4.6", + "storybook": "^7.5.0", "storybook-addon-mock": "^4.3.0", "ts-lit-plugin": "^1.2.1", "tslib": "^2.6.2", @@ -100,9 +101,9 @@ "vite-tsconfig-paths": "^4.2.1" }, "optionalDependencies": { - "@esbuild/darwin-arm64": "^0.19.4", + "@esbuild/darwin-arm64": "^0.19.5", "@esbuild/linux-amd64": "^0.18.11", - "@esbuild/linux-arm64": "^0.19.4" + "@esbuild/linux-arm64": "^0.19.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2402,9 +2403,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz", - "integrity": "sha512-Lviw8EzxsVQKpbS+rSt6/6zjn9ashUZ7Tbuvc2YENgRl0yZTktGlachZ9KMJUsVjZEGFVu336kl5lBgDN6PmpA==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", + "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", "cpu": [ "arm64" ], @@ -2481,9 +2482,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz", - "integrity": "sha512-ZWmWORaPbsPwmyu7eIEATFlaqm0QGt+joRE9sKcnVUG3oBbr/KYdNE2TnkzdQwX6EDRdg/x8Q4EZQTXoClUqqA==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", + "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", "cpu": [ "arm64" ], @@ -2855,9 +2856,9 @@ } }, "node_modules/@formatjs/intl-listformat": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.4.2.tgz", - "integrity": "sha512-+6bSVudEQkf12Hh7kuKt8Xv/MyFlqdwA4V4NLnTZW8uYdF9RxlOELDD0rPaOc2++TMKIzI5o6XXwHPvpL6VrPA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.0.tgz", + "integrity": "sha512-n9FsXGl1T2ZbX6wSyrzCDJHrbJR0YJ9ZNsAqUvHXfbY3nsOmGnSTf5+bkuIp1Xiywu7m1X1Pfm/Ngp/yK1H84A==", "dependencies": { "@formatjs/ecma402-abstract": "1.17.2", "@formatjs/intl-localematcher": "0.4.2", @@ -2882,9 +2883,9 @@ } }, "node_modules/@goauthentik/api": { - "version": "2023.8.3-1696847703", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.8.3-1696847703.tgz", - "integrity": "sha512-RsOANX4L6RHaGXvMhJNq9g+E0ZLW3cwgl/t5CyQxLYvWgmVvZU4t78hxlOF7vFREoO5nhZUZnOOlD2+n5gOqLg==" + "version": "2023.8.3-1697651039", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.8.3-1697651039.tgz", + "integrity": "sha512-ngpY7VocMkFlpfX3tX8U0KjmyZgzs5xcB3UiSDl+3A7/+j0IPv7eaYwYCeMsHj0//xUDEfiF5a/cTVweEAO5ew==" }, "node_modules/@hcaptcha/types": { "version": "1.0.3", @@ -4416,9 +4417,9 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.5.tgz", - "integrity": "sha512-xY8r/A9oisSeSuLCTfhssyDjo9Vp/eDiRLXkg1MXCcEEgEjPmLU+ZyDB20OOD0NlyDa/8SGbK5uIggF5XTx77w==", + "version": "25.0.7", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", + "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -4426,7 +4427,7 @@ "estree-walker": "^2.0.2", "glob": "^8.0.3", "is-reference": "1.2.1", - "magic-string": "^0.27.0" + "magic-string": "^0.30.3" }, "engines": { "node": ">=14.0.0" @@ -4466,13 +4467,13 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.3.tgz", - "integrity": "sha512-je7fu05B800IrMlWjb2wzJcdXzHYW46iTipfChnBDbIbDXhASZs27W1B58T2Yf45jZtJUONegpbce+9Ut2Ti/Q==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.4.tgz", + "integrity": "sha512-E2hmRnlh09K8HGT0rOnnri9OTh+BILGr7NVJGB30S4E3cLRn3J0xjdiyOZ74adPs4NiAMgrjUMGAZNJDBgsdmQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.27.0" + "magic-string": "^0.30.3" }, "engines": { "node": ">=14.0.0" @@ -4557,9 +4558,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.0.2.tgz", - "integrity": "sha512-xDvk1pT4vaPU2BOLy0MqHMdYZyntqpaBf8RhBiezlqG9OjY8F50TyctHo8znigYKd+QCFhCmlmXHOL/LoaOl3w==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.1.4.tgz", + "integrity": "sha512-WlzkuFvpKl6CLFdc3V6ESPt7gq5Vrimd2Yv9IzKXdOpgbH4cdDSS1JLiACX8toygihtH5OlxyQzhXOph7Ovlpw==", "cpu": [ "arm" ], @@ -4570,9 +4571,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.0.2.tgz", - "integrity": "sha512-lqCglytY3E6raze27DD9VQJWohbwCxzqs9aSHcj5X/8hJpzZfNdbsr4Ja9Hqp6iPyF53+5PtPx0pKRlkSvlHZg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.1.4.tgz", + "integrity": "sha512-D1e+ABe56T9Pq2fD+R3ybe1ylCDzu3tY4Qm2Mj24R9wXNCq35+JbFbOpc2yrroO2/tGhTobmEl2Bm5xfE/n8RA==", "cpu": [ "arm64" ], @@ -4583,9 +4584,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.0.2.tgz", - "integrity": "sha512-nkBKItS6E6CCzvRwgiKad+j+1ibmL7SIInj7oqMWmdkCjiSX6VeVZw2mLlRKIUL+JjsBgpATTfo7BiAXc1v0jA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.1.4.tgz", + "integrity": "sha512-7vTYrgEiOrjxnjsgdPB+4i7EMxbVp7XXtS+50GJYj695xYTTEMn3HZVEvgtwjOUkAP/Q4HDejm4fIAjLeAfhtg==", "cpu": [ "arm64" ], @@ -4596,9 +4597,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.0.2.tgz", - "integrity": "sha512-vX2C8xvWPIbpEgQht95+dY6BReKAvtDgPDGi0XN0kWJKkm4WdNmq5dnwscv/zxvi+n6jUTBhs6GtpkkWT4q8Gg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.1.4.tgz", + "integrity": "sha512-eGJVZScKSLZkYjhTAESCtbyTBq9SXeW9+TX36ki5gVhDqJtnQ5k0f9F44jNK5RhAMgIj0Ht9+n6HAgH0gUUyWQ==", "cpu": [ "x64" ], @@ -4609,9 +4610,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.0.2.tgz", - "integrity": "sha512-DVFIfcHOjgmeHOAqji4xNz2wczt1Bmzy9MwBZKBa83SjBVO/i38VHDR+9ixo8QpBOiEagmNw12DucG+v55tCrg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.1.4.tgz", + "integrity": "sha512-HnigYSEg2hOdX1meROecbk++z1nVJDpEofw9V2oWKqOWzTJlJf1UXVbDE6Hg30CapJxZu5ga4fdAQc/gODDkKg==", "cpu": [ "arm" ], @@ -4622,9 +4623,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.0.2.tgz", - "integrity": "sha512-GCK/a9ItUxPI0V5hQEJjH4JtOJO90GF2Hja7TO+EZ8rmkGvEi8/ZDMhXmcuDpQT7/PWrTT9RvnG8snMd5SrhBQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.1.4.tgz", + "integrity": "sha512-TzJ+N2EoTLWkaClV2CUhBlj6ljXofaYzF/R9HXqQ3JCMnCHQZmQnbnZllw7yTDp0OG5whP4gIPozR4QiX+00MQ==", "cpu": [ "arm64" ], @@ -4635,9 +4636,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.0.2.tgz", - "integrity": "sha512-cLuBp7rOjIB1R2j/VazjCmHC7liWUur2e9mFflLJBAWCkrZ+X0+QwHLvOQakIwDymungzAKv6W9kHZnTp/Mqrg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.1.4.tgz", + "integrity": "sha512-aVPmNMdp6Dlo2tWkAduAD/5TL/NT5uor290YvjvFvCv0Q3L7tVdlD8MOGDL+oRSw5XKXKAsDzHhUOPUNPRHVTQ==", "cpu": [ "arm64" ], @@ -4648,9 +4649,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.0.2.tgz", - "integrity": "sha512-Zqw4iVnJr2naoyQus0yLy7sLtisCQcpdMKUCeXPBjkJtpiflRime/TMojbnl8O3oxUAj92mxr+t7im/RbgA20w==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.1.4.tgz", + "integrity": "sha512-77Fb79ayiDad0grvVsz4/OB55wJRyw9Ao+GdOBA9XywtHpuq5iRbVyHToGxWquYWlEf6WHFQQnFEttsAzboyKg==", "cpu": [ "x64" ], @@ -4661,9 +4662,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.0.2.tgz", - "integrity": "sha512-jJRU9TyUD/iMqjf8aLAp7XiN3pIj5v6Qcu+cdzBfVTKDD0Fvua4oUoK8eVJ9ZuKBEQKt3WdlcwJXFkpmMLk6kg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.1.4.tgz", + "integrity": "sha512-/t6C6niEQTqmQTVTD9TDwUzxG91Mlk69/v0qodIPUnjjB3wR4UA3klg+orR2SU3Ux2Cgf2pWPL9utK80/1ek8g==", "cpu": [ "x64" ], @@ -4674,9 +4675,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.0.2.tgz", - "integrity": "sha512-ZkS2NixCxHKC4zbOnw64ztEGGDVIYP6nKkGBfOAxEPW71Sji9v8z3yaHNuae/JHPwXA+14oDefnOuVfxl59SmQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.1.4.tgz", + "integrity": "sha512-ZY5BHHrOPkMbCuGWFNpJH0t18D2LU6GMYKGaqaWTQ3CQOL57Fem4zE941/Ek5pIsVt70HyDXssVEFQXlITI5Gg==", "cpu": [ "arm64" ], @@ -4687,9 +4688,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.0.2.tgz", - "integrity": "sha512-3SKjj+tvnZ0oZq2BKB+fI+DqYI83VrRzk7eed8tJkxeZ4zxJZcLSE8YDQLYGq1tZAnAX+H076RHHB4gTZXsQzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.1.4.tgz", + "integrity": "sha512-XG2mcRfFrJvYyYaQmvCIvgfkaGinfXrpkBuIbJrTl9SaIQ8HumheWTIwkNz2mktCKwZfXHQNpO7RgXLIGQ7HXA==", "cpu": [ "ia32" ], @@ -4700,9 +4701,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.0.2.tgz", - "integrity": "sha512-MBdJIOxRauKkry7t2q+rTHa3aWjVez2eioWg+etRVS3dE4tChhmt5oqZYr48R6bPmcwEhxQr96gVRfeQrLbqng==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.1.4.tgz", + "integrity": "sha512-ANFqWYPwkhIqPmXw8vm0GpBEHiPpqcm99jiiAp71DbCSqLDhrtr019C5vhD0Bw4My+LmMvciZq6IsWHqQpl2ZQ==", "cpu": [ "x64" ], @@ -4713,13 +4714,13 @@ ] }, "node_modules/@sentry-internal/tracing": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.73.0.tgz", - "integrity": "sha512-ig3WL/Nqp8nRQ52P205NaypGKNfIl/G+cIqge9xPW6zfRb5kJdM1YParw9GSJ1SPjEZBkBORGAML0on5H2FILw==", + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.74.1.tgz", + "integrity": "sha512-nNaiZreQxCitG2PzYPaC7XtyA9OMsETGYMKAtiK4p62/uTmeYbsBva9BoNx1XeiHRwbrVQYRMKQ9nV5e2jS4/A==", "dependencies": { - "@sentry/core": "7.73.0", - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0", + "@sentry/core": "7.74.1", + "@sentry/types": "7.74.1", + "@sentry/utils": "7.74.1", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -4727,15 +4728,15 @@ } }, "node_modules/@sentry/browser": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.73.0.tgz", - "integrity": "sha512-e301hUixcJ5+HNKCJwajFF5smF4opXEFSclyWsJuFNufv5J/1C1SDhbwG2JjBt5zzdSoKWJKT1ewR6vpICyoDw==", + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.74.1.tgz", + "integrity": "sha512-OYWNne/KO60lOvkIpIlJUyiJt/9j8DGI57thSDFEYSmmbNqMitczUTBOaEStouvHKyfchqLZm1CZfWKt+z0VOA==", "dependencies": { - "@sentry-internal/tracing": "7.73.0", - "@sentry/core": "7.73.0", - "@sentry/replay": "7.73.0", - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0", + "@sentry-internal/tracing": "7.74.1", + "@sentry/core": "7.74.1", + "@sentry/replay": "7.74.1", + "@sentry/types": "7.74.1", + "@sentry/utils": "7.74.1", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -4743,12 +4744,12 @@ } }, "node_modules/@sentry/core": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.73.0.tgz", - "integrity": "sha512-9FEz4Gq848LOgVN2OxJGYuQqxv7cIVw69VlAzWHEm3njt8mjvlTq+7UiFsGRo84+59V2FQuHxzA7vVjl90WfSg==", + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.74.1.tgz", + "integrity": "sha512-LvEhOSfdIvwkr+PdlrT/aA/iOLhkXrSkvjqAQyogE4ddCWeYfS0NoirxNt1EaxMBAWKhYZRqzkA7WA4LDLbzlA==", "dependencies": { - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0", + "@sentry/types": "7.74.1", + "@sentry/utils": "7.74.1", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -4756,43 +4757,43 @@ } }, "node_modules/@sentry/replay": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.73.0.tgz", - "integrity": "sha512-a8IC9SowBisLYD2IdLkXzx7gN4iVwHDJhQvLp2B8ARs1PyPjJ7gCxSMHeGrYp94V0gOXtorNYkrxvuX8ayPROA==", + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.74.1.tgz", + "integrity": "sha512-qmbOl+jYdyhoHFbPp9WemKx8UojID5hVmuVLxNIP0ANqAwmE9OQEK9YFg2cf7L/TpKb1tqz0qLgi5MYIdcdpgQ==", "dependencies": { - "@sentry/core": "7.73.0", - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0" + "@sentry/core": "7.74.1", + "@sentry/types": "7.74.1", + "@sentry/utils": "7.74.1" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/tracing": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.73.0.tgz", - "integrity": "sha512-LOQR6Hkc8ZoflCXWtMlxTbCBEwv0MSOr3vesnRsmlFG8TW1YUIneU+wKnVxToWAZ8fq+6ubclnuIUKHfqTk/Tg==", + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.74.1.tgz", + "integrity": "sha512-YqhLMY28uukOR8FtoCMvzdzBYkTtwj/JHUensDEpTZG5OoQTjrcgttpL+WMaCBUy1MpOIo7FyLB5aoRq2U7AIA==", "dependencies": { - "@sentry-internal/tracing": "7.73.0" + "@sentry-internal/tracing": "7.74.1" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/types": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.73.0.tgz", - "integrity": "sha512-/v8++bly8jW7r4cP2wswYiiVpn7eLLcqwnfPUMeCQze4zj3F3nTRIKc9BGHzU0V+fhHa3RwRC2ksqTGq1oJMDg==", + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.74.1.tgz", + "integrity": "sha512-2jIuPc+YKvXqZETwr2E8VYnsH1zsSUR/wkIvg1uTVeVNyoowJv+YsOtCdeGyL2AwiotUBSPKu7O1Lz0kq5rMOQ==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.73.0.tgz", - "integrity": "sha512-h3ZK/qpf4k76FhJV9uiSbvMz3V/0Ovy94C+5/9UgPMVCJXFmVsdw8n/dwANJ7LupVPfYP23xFGgebDMFlK1/2w==", + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.74.1.tgz", + "integrity": "sha512-qUsqufuHYcy5gFhLZslLxA5kcEOkkODITXW3c7D+x+8iP/AJqa8v8CeUCVNS7RetHCuIeWAbbTClC4c411EwQg==", "dependencies": { - "@sentry/types": "7.73.0", + "@sentry/types": "7.74.1", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -4806,19 +4807,19 @@ "dev": true }, "node_modules/@storybook/addon-actions": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.4.6.tgz", - "integrity": "sha512-SsqZr3js5NinKPnC8AeNI7Ij+Q6fIl9tRdRmSulEgjksjOg7E5S1/Wsn5Bb2CCgj7MaX6VxGyC7s3XskQtDiIQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.5.0.tgz", + "integrity": "sha512-eeHIFpZXGyhkfmrbHRf3rndL+ppFqlKTgN74y+UfFaAWNUhV3caXxRbHV3BbcPSLkRAsNShBH9hTNTlUAHSVjA==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "polished": "^4.2.2", @@ -4846,13 +4847,13 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -4864,9 +4865,9 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -4877,9 +4878,9 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -4890,19 +4891,19 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -4921,17 +4922,17 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -4947,12 +4948,12 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -4966,13 +4967,13 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -4986,12 +4987,12 @@ } }, "node_modules/@storybook/addon-actions/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -5035,19 +5036,19 @@ "dev": true }, "node_modules/@storybook/addon-backgrounds": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.4.6.tgz", - "integrity": "sha512-+LHTZB/ZYMAzkyD5ZxSriBsqmsrvIaW/Nnd/BeuXGbkrVKKqM0qAKiFZAfjc2WchA1piVNy0/1Rsf+kuYCEiJw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.5.0.tgz", + "integrity": "sha512-Yu/eFHZIfyAhK28GKKcIBwj/9+hRem8pSdI3N0FJuOhErmaE0zg6VDUBzkgLa/Fn9SwC5PNyAeLAtxssg1KSNg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "memoizerific": "^1.11.3", "ts-dedent": "^2.0.0" }, @@ -5069,13 +5070,13 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -5087,9 +5088,9 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -5100,9 +5101,9 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -5113,19 +5114,19 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -5144,17 +5145,17 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -5170,12 +5171,12 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -5189,13 +5190,13 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -5209,12 +5210,12 @@ } }, "node_modules/@storybook/addon-backgrounds/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -5258,21 +5259,21 @@ "dev": true }, "node_modules/@storybook/addon-controls": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.4.6.tgz", - "integrity": "sha512-4lq3sycEUIsK8SUWDYc60QgF4vV9FZZ3lDr6M7j2W9bOnvGw49d2fbdlnq+bX1ZprZZ9VgglQpBAorQB3BXZRw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.5.0.tgz", + "integrity": "sha512-X56Pd+0GH1A8ddVsziJQaJ8qCaxsWK0aLCKH5li9GLtnyIGHvd5+KvvfYEbjTkeJv3d9J7X0D4uTAH1/dsmI8w==", "dev": true, "dependencies": { - "@storybook/blocks": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/core-common": "7.4.6", - "@storybook/core-events": "7.4.6", - "@storybook/manager-api": "7.4.6", - "@storybook/node-logger": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/blocks": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/core-common": "7.5.0", + "@storybook/core-events": "7.5.0", + "@storybook/manager-api": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -5294,13 +5295,13 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -5312,9 +5313,9 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -5325,9 +5326,9 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -5338,19 +5339,19 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -5369,17 +5370,17 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -5395,12 +5396,12 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -5414,13 +5415,13 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -5434,12 +5435,12 @@ } }, "node_modules/@storybook/addon-controls/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -5483,26 +5484,26 @@ "dev": true }, "node_modules/@storybook/addon-docs": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.4.6.tgz", - "integrity": "sha512-dLaub+XWFq4hChw+xfuF9yYg0Txp77FUawKoAigccfjWXx+OOhRV3XTuAcknpXkYq94GWynHgUFXosXT9kbDNA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.5.0.tgz", + "integrity": "sha512-lgrum81iJT+i85kO3uOR4wR1t05x4SmJLCB2cyYohCIafiOiV4FuyYFhvT9N6UhHByOfrWgpipKgKg6zsmV2eg==", "dev": true, "dependencies": { "@jest/transform": "^29.3.1", "@mdx-js/react": "^2.1.5", - "@storybook/blocks": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/csf-plugin": "7.4.6", - "@storybook/csf-tools": "7.4.6", + "@storybook/blocks": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/csf-plugin": "7.5.0", + "@storybook/csf-tools": "7.5.0", "@storybook/global": "^5.0.0", "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.4.6", - "@storybook/postinstall": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/react-dom-shim": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/node-logger": "7.5.0", + "@storybook/postinstall": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/react-dom-shim": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "fs-extra": "^11.1.0", "remark-external-links": "^8.0.0", "remark-slug": "^6.0.0", @@ -5518,13 +5519,13 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -5536,9 +5537,9 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -5549,9 +5550,9 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -5562,17 +5563,17 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -5588,13 +5589,13 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -5608,12 +5609,12 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -5638,24 +5639,24 @@ } }, "node_modules/@storybook/addon-essentials": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.4.6.tgz", - "integrity": "sha512-dWodufrt71TK7ELkeIvVae/x4PzECUlbOm57Iqqt4yQCyR291CgvI4PjeB8un2HbpcXCGZ+N/Oj3YkytvzBi4A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.5.0.tgz", + "integrity": "sha512-CKPHdQBP6psTVb3NHsP8cWSUcAA4kwzT8SrJxKddn4ecqmWJWeZo5g5y3WuqVQHlv3edpluJLQYehcVibcljag==", "dev": true, "dependencies": { - "@storybook/addon-actions": "7.4.6", - "@storybook/addon-backgrounds": "7.4.6", - "@storybook/addon-controls": "7.4.6", - "@storybook/addon-docs": "7.4.6", - "@storybook/addon-highlight": "7.4.6", - "@storybook/addon-measure": "7.4.6", - "@storybook/addon-outline": "7.4.6", - "@storybook/addon-toolbars": "7.4.6", - "@storybook/addon-viewport": "7.4.6", - "@storybook/core-common": "7.4.6", - "@storybook/manager-api": "7.4.6", - "@storybook/node-logger": "7.4.6", - "@storybook/preview-api": "7.4.6", + "@storybook/addon-actions": "7.5.0", + "@storybook/addon-backgrounds": "7.5.0", + "@storybook/addon-controls": "7.5.0", + "@storybook/addon-docs": "7.5.0", + "@storybook/addon-highlight": "7.5.0", + "@storybook/addon-measure": "7.5.0", + "@storybook/addon-outline": "7.5.0", + "@storybook/addon-toolbars": "7.5.0", + "@storybook/addon-viewport": "7.5.0", + "@storybook/core-common": "7.5.0", + "@storybook/manager-api": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/preview-api": "7.5.0", "ts-dedent": "^2.0.0" }, "funding": { @@ -5668,13 +5669,13 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -5686,9 +5687,9 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -5699,9 +5700,9 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -5712,19 +5713,19 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -5743,17 +5744,17 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -5769,12 +5770,12 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -5788,13 +5789,13 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -5808,12 +5809,12 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -5857,14 +5858,14 @@ "dev": true }, "node_modules/@storybook/addon-highlight": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.4.6.tgz", - "integrity": "sha512-zCufxxD2KS5VwczxfkcBxe1oR/juTTn2H1Qm8kYvWCJQx3UxzX0+G9cwafbpV7eivqaufLweEwROkH+0KjAtkQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.5.0.tgz", + "integrity": "sha512-6SlEkGCZ/LnEcbN6oE2Au3fgI9VfULErWQ36bx+sV6WWTb1EoooiD7ZJJzobrcOAriSyfWoctO5DF4W+X9I8lg==", "dev": true, "dependencies": { - "@storybook/core-events": "7.4.6", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/preview-api": "7.4.6" + "@storybook/preview-api": "7.5.0" }, "funding": { "type": "opencollective", @@ -5872,13 +5873,13 @@ } }, "node_modules/@storybook/addon-highlight/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -5890,9 +5891,9 @@ } }, "node_modules/@storybook/addon-highlight/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -5903,9 +5904,9 @@ } }, "node_modules/@storybook/addon-highlight/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -5916,17 +5917,17 @@ } }, "node_modules/@storybook/addon-highlight/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -5942,12 +5943,12 @@ } }, "node_modules/@storybook/addon-highlight/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -5958,19 +5959,19 @@ } }, "node_modules/@storybook/addon-links": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.4.6.tgz", - "integrity": "sha512-BPygElZKX+CPI9Se6GJNk1dYc5oxuhA+vHigO1tBqhiM6VkHyFP3cvezJNQvpNYhkUnu3cxnZXb3UJnlRbPY3g==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.5.0.tgz", + "integrity": "sha512-1j0I80k8V1sSGN3faduj9uFk0ThgT4qAYyA/5q2YYA4y6V/K8ywJVOR3nv5j7ueTeBD/gUaoncn+NosusrhRNQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/router": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/router": "7.5.0", + "@storybook/types": "7.5.0", "prop-types": "^15.7.2", "ts-dedent": "^2.0.0" }, @@ -5992,13 +5993,13 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6010,9 +6011,9 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6023,9 +6024,9 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -6036,19 +6037,19 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -6067,17 +6068,17 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -6093,12 +6094,12 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -6112,13 +6113,13 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -6132,12 +6133,12 @@ } }, "node_modules/@storybook/addon-links/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -6181,18 +6182,18 @@ "dev": true }, "node_modules/@storybook/addon-measure": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.4.6.tgz", - "integrity": "sha512-nCymMLaHnxv8TE3yEM1A9Tulb1NuRXRNmtsdHTkjv7P1aWCxZo8A/GZaottKe/GLT8jSRjZ+dnpYWrbAhw6wTQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.5.0.tgz", + "integrity": "sha512-zzHrQpn+burEr37hV1QV7yA1M33wBa38dUe+RLNYkS9g22BXYYZ/uVUhljpmA9DhZCUNJqYbXWi+ad4XMPE6+Q==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/types": "7.5.0", "tiny-invariant": "^1.3.1" }, "funding": { @@ -6213,13 +6214,13 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6231,9 +6232,9 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6244,9 +6245,9 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -6257,19 +6258,19 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -6288,17 +6289,17 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -6314,12 +6315,12 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -6333,13 +6334,13 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -6353,12 +6354,12 @@ } }, "node_modules/@storybook/addon-measure/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -6402,18 +6403,18 @@ "dev": true }, "node_modules/@storybook/addon-outline": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.4.6.tgz", - "integrity": "sha512-errNUblRVDLpuEaHQPr/nsrnsUkD2ARmXawkRvizgDWLIDMDJYjTON3MUCaVx3x+hlZ3I6X//G5TVcma8tCc8A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.5.0.tgz", + "integrity": "sha512-iVcyFi2N2NEZRytUg8wSiXS9UE9wA8/prs/sIsQ7Y34vHm1UaqAd8KxCE/fhHFNYw4UyHEEDUyTfci/jNrNQYA==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/types": "7.5.0", "ts-dedent": "^2.0.0" }, "funding": { @@ -6434,13 +6435,13 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6452,9 +6453,9 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6465,9 +6466,9 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -6478,19 +6479,19 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -6509,17 +6510,17 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -6535,12 +6536,12 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -6554,13 +6555,13 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -6574,12 +6575,12 @@ } }, "node_modules/@storybook/addon-outline/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -6623,16 +6624,16 @@ "dev": true }, "node_modules/@storybook/addon-toolbars": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.4.6.tgz", - "integrity": "sha512-L9m2FBcKeteGq7qIYsMJr0LEfiH7Wdrv5IDcldZTn68eZUJTh1p4GdJZcOmzX1P5IFRr76hpu03iWsNlWQjpbQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.5.0.tgz", + "integrity": "sha512-RLONWIJE7myVL3DzWZDWnnmb53C1OitCiO3mDt678xyK5ZrFCOV9cznckXASx1wNJVt3P9OOW1N2UY7wul72+Q==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/theming": "7.4.6" + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/theming": "7.5.0" }, "funding": { "type": "opencollective", @@ -6652,13 +6653,13 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6670,9 +6671,9 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6683,9 +6684,9 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -6696,19 +6697,19 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -6727,17 +6728,17 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -6753,12 +6754,12 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -6772,13 +6773,13 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -6792,12 +6793,12 @@ } }, "node_modules/@storybook/addon-toolbars/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -6841,18 +6842,18 @@ "dev": true }, "node_modules/@storybook/addon-viewport": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.4.6.tgz", - "integrity": "sha512-INDtk54j7bi7NgxMfd2ATmbA0J7nAd6X8itMkLIyPuPJtx8bYHPDORyemDOd0AojgmAdTOAyUtDYdI/PFeo4Cw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.5.0.tgz", + "integrity": "sha512-NXnjHQFKgeFsWOaJE0fl2THgejxDqx8axy4Prtc3ePcoVa/UrMu11G3iEcCaLhDJU7RDNM6CODgifYpH6gyKWg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/theming": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/theming": "7.5.0", "memoizerific": "^1.11.3", "prop-types": "^15.7.2" }, @@ -6874,13 +6875,13 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6892,9 +6893,9 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6905,9 +6906,9 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -6918,19 +6919,19 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -6949,17 +6950,17 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -6975,12 +6976,12 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -6994,13 +6995,13 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -7014,12 +7015,12 @@ } }, "node_modules/@storybook/addon-viewport/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -7272,22 +7273,22 @@ } }, "node_modules/@storybook/blocks": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.4.6.tgz", - "integrity": "sha512-HxBSAeOiTZW2jbHQlo1upRWFgoMsaAyKijUFf5MwwMNIesXCuuTGZDJ3xTABwAVLK2qC9Ektfbo0CZCiPVuDRQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.5.0.tgz", + "integrity": "sha512-4poS7lQVKhitWKl0TPECMszOMtNamsbNvZdAZ188U/p1EzTrqLg+RT9HtsB8q8Y0owx29Nh5LdfhNOddpx23ig==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/components": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/components": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", - "@storybook/docs-tools": "7.4.6", + "@storybook/docs-tools": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "@types/lodash": "^4.14.167", "color-convert": "^2.0.1", "dequal": "^2.0.2", @@ -7311,13 +7312,13 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -7329,9 +7330,9 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -7342,9 +7343,9 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -7355,19 +7356,19 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -7386,17 +7387,17 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -7412,12 +7413,12 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -7431,13 +7432,13 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -7451,12 +7452,12 @@ } }, "node_modules/@storybook/blocks/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -7500,15 +7501,15 @@ "dev": true }, "node_modules/@storybook/builder-manager": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.4.6.tgz", - "integrity": "sha512-zylZCD2rmyLOOFBFmUgtJg6UNUKmRNgXiig1XApzS2TkIbTZP827DsVEUl0ey/lskCe0uArkrEBR6ICba8p/Rw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.5.0.tgz", + "integrity": "sha512-nj+n36i7Mds4RIyGJqvOB+Z47zfgbMes+6Gd6reT1vC22Yda5nAITnd2vxbYfv/sUPhIBBfuFZ/eogomgYCjKg==", "dev": true, "dependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.4.6", - "@storybook/manager": "7.4.6", - "@storybook/node-logger": "7.4.6", + "@storybook/core-common": "7.5.0", + "@storybook/manager": "7.5.0", + "@storybook/node-logger": "7.5.0", "@types/ejs": "^3.1.1", "@types/find-cache-dir": "^3.2.1", "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", @@ -7542,20 +7543,19 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.4.6.tgz", - "integrity": "sha512-xV9STYK+TkqWWTf2ydm6jx+7P70fjD2UPd1XTUw08uKszIjhuuxk+bG/OF5R1E25mPunAKXm6kBFh351AKejBg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.5.0.tgz", + "integrity": "sha512-XqiXECAhIDhUryhcPfWfmrvCA2R9p4cebXdyH5Op17yKQ10Bp+OxDWXZlOY/PHdq2KBVhC8CF3Yp7JXCWk8BHw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-common": "7.4.6", - "@storybook/csf-plugin": "7.4.6", - "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.4.6", - "@storybook/preview": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-common": "7.5.0", + "@storybook/csf-plugin": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/preview": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/types": "7.5.0", "@types/find-cache-dir": "^3.2.1", "browser-assert": "^1.2.1", "es-module-lexer": "^0.9.3", @@ -7563,8 +7563,6 @@ "find-cache-dir": "^3.0.0", "fs-extra": "^11.1.0", "magic-string": "^0.30.0", - "remark-external-links": "^8.0.0", - "remark-slug": "^6.0.0", "rollup": "^2.25.0 || ^3.3.0" }, "funding": { @@ -7574,7 +7572,7 @@ "peerDependencies": { "@preact/preset-vite": "*", "typescript": ">= 4.3.x", - "vite": "^3.0.0 || ^4.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0", "vite-plugin-glimmerx": "*" }, "peerDependenciesMeta": { @@ -7590,13 +7588,13 @@ } }, "node_modules/@storybook/builder-vite/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -7608,9 +7606,9 @@ } }, "node_modules/@storybook/builder-vite/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -7621,9 +7619,9 @@ } }, "node_modules/@storybook/builder-vite/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -7634,17 +7632,17 @@ } }, "node_modules/@storybook/builder-vite/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -7660,12 +7658,12 @@ } }, "node_modules/@storybook/builder-vite/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -7689,18 +7687,6 @@ "node": ">=14.14" } }, - "node_modules/@storybook/builder-vite/node_modules/magic-string": { - "version": "0.30.4", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", - "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@storybook/builder-vite/node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -7737,23 +7723,23 @@ } }, "node_modules/@storybook/cli": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.4.6.tgz", - "integrity": "sha512-rRwaH8pOL+FHz/pJMEkNpMH2xvZvWsrl7obBYw26NQiHmiVSAkfHJicndSN1mwc+p5w+9iXthrgzbLtSAOSvkA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.5.0.tgz", + "integrity": "sha512-f14q6sqHhDf7bFS0o/ZTgN2tM00Q0cMGMmGFXTQSCh0HXJUS4ujy/FADL+x62wUylIdr1HkIw+ONWMMqHuenEA==", "dev": true, "dependencies": { "@babel/core": "^7.22.9", "@babel/preset-env": "^7.22.9", "@babel/types": "^7.22.5", "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.4.6", - "@storybook/core-common": "7.4.6", - "@storybook/core-events": "7.4.6", - "@storybook/core-server": "7.4.6", - "@storybook/csf-tools": "7.4.6", - "@storybook/node-logger": "7.4.6", - "@storybook/telemetry": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/codemod": "7.5.0", + "@storybook/core-common": "7.5.0", + "@storybook/core-events": "7.5.0", + "@storybook/core-server": "7.5.0", + "@storybook/csf-tools": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/telemetry": "7.5.0", + "@storybook/types": "7.5.0", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -7794,13 +7780,13 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -7812,9 +7798,9 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -7825,9 +7811,9 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -7838,12 +7824,12 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -7991,18 +7977,18 @@ } }, "node_modules/@storybook/codemod": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.4.6.tgz", - "integrity": "sha512-lxmwEpwksCaAq96APN2YlooSDfKjJ1vKzN5Ni2EqQzf2TEXl7XQjLacHd7OOaII1kfsy+D5gNG4N5wBo7Ub30g==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.5.0.tgz", + "integrity": "sha512-QdjFdD1OK+LqhYwNMh60/kgSt9VZIgH2TBUeXrPlCK6gfcZBrCB0ktgtuM8Zk/ROktq09pZoVDxqFi0AbEUPew==", "dev": true, "dependencies": { "@babel/core": "^7.22.9", "@babel/preset-env": "^7.22.9", "@babel/types": "^7.22.5", "@storybook/csf": "^0.1.0", - "@storybook/csf-tools": "7.4.6", - "@storybook/node-logger": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/csf-tools": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/types": "7.5.0", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "globby": "^11.0.2", @@ -8017,13 +8003,13 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8035,9 +8021,9 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8048,9 +8034,9 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8061,12 +8047,12 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -8092,18 +8078,18 @@ } }, "node_modules/@storybook/components": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.4.6.tgz", - "integrity": "sha512-nIRBhewAgrJJVafyCzuaLx1l+YOfvvD5dOZ0JxZsxJsefOdw1jFpUqUZ5fIpQ2moyvrR0mAUFw378rBfMdHz5Q==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.5.0.tgz", + "integrity": "sha512-6lmZ6PbS27xN32vTJ/NvgaiKkFIQRzZuBeBIg2u+FoAEgCiCwRXjZKe/O8NZC2Xr0uf97+7U2P0kD4Hwr9SNhw==", "dev": true, "dependencies": { "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "memoizerific": "^1.11.3", "use-resize-observer": "^9.1.0", "util-deprecate": "^1.0.2" @@ -8118,13 +8104,13 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8136,9 +8122,9 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8149,9 +8135,9 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8162,13 +8148,13 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -8182,12 +8168,12 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -8198,13 +8184,13 @@ } }, "node_modules/@storybook/core-client": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.4.6.tgz", - "integrity": "sha512-tfgxAHeCvMcs6DsVgtb4hQSDaCHeAPJOsoyhb47eDQfk4OmxzriM0qWucJV5DePSMi+KutX/rN2u0JxfOuN68g==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.5.0.tgz", + "integrity": "sha512-lnlPhsHnjK3tQ6jgTL/4TqIsxqznMQ0p7lSnUfhfccc2lGtMO/Ez/xIiTGoJQssJxuJE3d4sj3wRgYvuTDGQYw==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/preview-api": "7.4.6" + "@storybook/client-logger": "7.5.0", + "@storybook/preview-api": "7.5.0" }, "funding": { "type": "opencollective", @@ -8212,13 +8198,13 @@ } }, "node_modules/@storybook/core-client/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8230,9 +8216,9 @@ } }, "node_modules/@storybook/core-client/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8243,9 +8229,9 @@ } }, "node_modules/@storybook/core-client/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8256,17 +8242,17 @@ } }, "node_modules/@storybook/core-client/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -8282,12 +8268,12 @@ } }, "node_modules/@storybook/core-client/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -8298,21 +8284,21 @@ } }, "node_modules/@storybook/core-common": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.4.6.tgz", - "integrity": "sha512-05MJFmOM86qvTLtgDskokIFz9txe0Lbhq4L3by1FtF0GwgH+p+W6I94KI7c6ANER+kVZkXQZhiRzwBFnVTW+Cg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.5.0.tgz", + "integrity": "sha512-Gw3/rzRb5+XbwqBcr2ZNaIYGEp+WNTwaBOnMs4yp2SCrNIb0P+i3BxlVQdgABaq43EI3/bksowT6hei0jyhGhw==", "dev": true, "dependencies": { - "@storybook/core-events": "7.4.6", - "@storybook/node-logger": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/core-events": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/types": "7.5.0", "@types/find-cache-dir": "^3.2.1", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", "@types/pretty-hrtime": "^1.0.0", "chalk": "^4.1.0", "esbuild": "^0.18.0", - "esbuild-register": "^3.4.0", + "esbuild-register": "^3.5.0", "file-system-cache": "2.3.0", "find-cache-dir": "^3.0.0", "find-up": "^5.0.0", @@ -8333,13 +8319,13 @@ } }, "node_modules/@storybook/core-common/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8351,9 +8337,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8364,9 +8350,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8377,12 +8363,12 @@ } }, "node_modules/@storybook/core-common/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -8393,9 +8379,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@types/node": { - "version": "16.18.57", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.57.tgz", - "integrity": "sha512-piPoDozdPaX1hNWFJQzzgWqE40gh986VvVx/QO9RU4qYRE55ld7iepDVgZ3ccGUw0R4wge0Oy1dd+3xOQNkkUQ==", + "version": "18.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.5.tgz", + "integrity": "sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==", "dev": true }, "node_modules/@storybook/core-common/node_modules/ansi-styles": { @@ -8525,28 +8511,28 @@ } }, "node_modules/@storybook/core-server": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.4.6.tgz", - "integrity": "sha512-jqmRTGCJ1W0WReImivkisPVaLFT5sjtLnFoAk0feHp6QS5j7EYOPN7CYzliyQmARWTLUEXOVaFf3VD6nJZQhJQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.5.0.tgz", + "integrity": "sha512-7QT8uzwSJOsv9PASQ6ywepYkcEYFB7+S7Cj/0nFMh3Vl9vW96LXvEHLAo9CUhSxdEKWeTnD8DS5+j90dLhQFCA==", "dev": true, "dependencies": { "@aw-web-design/x-default-browser": "1.4.126", "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.4.6", - "@storybook/channels": "7.4.6", - "@storybook/core-common": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/builder-manager": "7.5.0", + "@storybook/channels": "7.5.0", + "@storybook/core-common": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", - "@storybook/csf-tools": "7.4.6", + "@storybook/csf-tools": "7.5.0", "@storybook/docs-mdx": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/manager": "7.4.6", - "@storybook/node-logger": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/telemetry": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/telemetry": "7.5.0", + "@storybook/types": "7.5.0", "@types/detect-port": "^1.3.0", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "@types/pretty-hrtime": "^1.0.0", "@types/semver": "^7.3.4", "better-opn": "^3.0.2", @@ -8578,13 +8564,13 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8596,9 +8582,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8609,9 +8595,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8622,17 +8608,17 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -8648,12 +8634,12 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -8664,9 +8650,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "16.18.57", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.57.tgz", - "integrity": "sha512-piPoDozdPaX1hNWFJQzzgWqE40gh986VvVx/QO9RU4qYRE55ld7iepDVgZ3ccGUw0R4wge0Oy1dd+3xOQNkkUQ==", + "version": "18.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.5.tgz", + "integrity": "sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==", "dev": true }, "node_modules/@storybook/core-server/node_modules/ansi-styles": { @@ -8778,12 +8764,12 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.4.6.tgz", - "integrity": "sha512-yi7Qa4NSqKOyiJTWCxlB0ih2ijXq6oY5qZKW6MuMMBP14xJNRGLbH5KabpfXgN2T7YECcOWG1uWaGj2veJb1KA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.5.0.tgz", + "integrity": "sha512-kghaEFYvQISdAjQddeicSuvBFMeuuLNtpmMkuoLQzULF7e/Tws6zLCYsjGevqlnqXD0iW2XM/j9q4M5L/mWc5A==", "dev": true, "dependencies": { - "@storybook/csf-tools": "7.4.6", + "@storybook/csf-tools": "7.5.0", "unplugin": "^1.3.1" }, "funding": { @@ -8792,9 +8778,9 @@ } }, "node_modules/@storybook/csf-tools": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.4.6.tgz", - "integrity": "sha512-ocKpcIUtTBy6hlLY34RUFQyX403cWpB2gGfqvkHbpGe2BQj7EyV0zpWnjsfVxvw+M9OWlCdxHWDOPUgXM33ELw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.5.0.tgz", + "integrity": "sha512-KOHbFNSwwc7KTdNz/6yO7S2pxbr7sH6nqfolS6/l+pod45WvRH3VhyqlDIIeX7ESIhfCw87ExC96hNDL3TojCw==", "dev": true, "dependencies": { "@babel/generator": "^7.22.9", @@ -8802,7 +8788,7 @@ "@babel/traverse": "^7.22.8", "@babel/types": "^7.22.5", "@storybook/csf": "^0.1.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -8813,13 +8799,13 @@ } }, "node_modules/@storybook/csf-tools/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8831,9 +8817,9 @@ } }, "node_modules/@storybook/csf-tools/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8844,9 +8830,9 @@ } }, "node_modules/@storybook/csf-tools/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8857,12 +8843,12 @@ } }, "node_modules/@storybook/csf-tools/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -8893,14 +8879,14 @@ "dev": true }, "node_modules/@storybook/docs-tools": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.4.6.tgz", - "integrity": "sha512-nZj1L/8WwKWWJ41FW4MaKGajZUtrhnr9UwflRCkQJaWhAKmDfOb5M5TqI93uCOULpFPOm5wpoMBz2IHInQ2Lrg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.5.0.tgz", + "integrity": "sha512-NFhqbXj6Wv5YypMwDkt0z9xcfWD7M3wZhr8Z9XcXDlUUPjBrdv0cHt3rfHwEXpTfFyunbK41KQZZ3JkjiAjgTg==", "dev": true, "dependencies": { - "@storybook/core-common": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/core-common": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/types": "7.5.0", "@types/doctrine": "^0.0.3", "doctrine": "^3.0.0", "lodash": "^4.17.21" @@ -8911,13 +8897,13 @@ } }, "node_modules/@storybook/docs-tools/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8929,9 +8915,9 @@ } }, "node_modules/@storybook/docs-tools/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8942,9 +8928,9 @@ } }, "node_modules/@storybook/docs-tools/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8955,17 +8941,17 @@ } }, "node_modules/@storybook/docs-tools/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -8981,12 +8967,12 @@ } }, "node_modules/@storybook/docs-tools/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -9003,9 +8989,9 @@ "dev": true }, "node_modules/@storybook/manager": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.4.6.tgz", - "integrity": "sha512-kA1hUDxpn1i2SO9OinvLvVXDeL4xgJkModp+pbE8IXv4NJWReNq1ecMeQCzPLS3Sil2gnrullQ9uYXsnZ9bxxA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.5.0.tgz", + "integrity": "sha512-M4h4b0Y4aZ1sRGaZuJXgvPZHqu7vN/wgWB5yPcSwJqH1+DlPxYXYnPKGERgaEUUVKJV3oWQD2qZ+UpDeTgI5UQ==", "dev": true, "funding": { "type": "opencollective", @@ -9087,9 +9073,9 @@ "dev": true }, "node_modules/@storybook/node-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.4.6.tgz", - "integrity": "sha512-djZb310Q27GviDug1XBv0jOEDLCiwr4hhDE0aifCEKZpfNCi/EaP31nbWimFzZwxu4hE/YAPWExzScruR1zw9Q==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.5.0.tgz", + "integrity": "sha512-Og3hdB1bjpVCXhmlhvpgVxUfCQGd0DCguXf5qhn2kX4a+D++dxJ8YqzVJ5JQCacI9bCKITV6W9JSGseWcBaXBg==", "dev": true, "funding": { "type": "opencollective", @@ -9097,9 +9083,9 @@ } }, "node_modules/@storybook/postinstall": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.4.6.tgz", - "integrity": "sha512-TqI5BucPAGRWrkh55BYiG2/gHLFtC0In4cuu0GsUzB/1jc4i51npLRorCwhmT7r7YliGl5F7JaP0Bni/qHN3Lg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.5.0.tgz", + "integrity": "sha512-SHpBItwar7qDZO7BBSqTNQK0yNy+RUROZUhW6wlVvsgVhIGF1bgA4pgpW1iMyfPmmGyNekE1BJjN+v8rjq9s6A==", "dev": true, "funding": { "type": "opencollective", @@ -9107,9 +9093,9 @@ } }, "node_modules/@storybook/preview": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.4.6.tgz", - "integrity": "sha512-2RPXusJ4CTDrIipIKKvbotD7fP0+8VzoFjImunflIrzN9rni+2rq5eMjqlXAaB+77w064zIR4uDUzI9fxsMDeQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.5.0.tgz", + "integrity": "sha512-KPhx43pRgIb6UhqjsF0sUG5c3GG2dwzTzjN1/sj0QbPMghZ3b7xKGrCu6VSlsXoWQtcwisMHETFnowk0Ba/AMg==", "dev": true, "funding": { "type": "opencollective", @@ -9203,9 +9189,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.4.6.tgz", - "integrity": "sha512-DSq8l9FDocUF1ooVI+TF83pddj1LynE/Hv0/y8XZhc3IgJ/HkuOQuUmfz29ezgfAi9gFYUR8raTIBi3/xdoRmw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.5.0.tgz", + "integrity": "sha512-OzJhXg1En/9D9vKvD2t0EcYcuHFzrLTA9kEUWt/eP3Ww41kndfJoZca33JZr17iuKksVAZ8ucETMnkL3yO+ybA==", "dev": true, "funding": { "type": "opencollective", @@ -9237,14 +9223,14 @@ } }, "node_modules/@storybook/telemetry": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.4.6.tgz", - "integrity": "sha512-c8p/C1NIH8EMBviZkBCx8MMDk6rrITJ+b29DEp5MaWSRlklIVyhGiC4RPIRv6sxJwlD41PnqWVFtfu2j2eXLdQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.5.0.tgz", + "integrity": "sha512-dvc1cjxHYGNfLEvh8eQI/R2KtMft0kUs6TJ2uXZdIX4+WqWG6mfn75sP8eyC1tcjkdslS6AmFWTfgt9EVcIPQA==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-common": "7.4.6", - "@storybook/csf-tools": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-common": "7.5.0", + "@storybook/csf-tools": "7.5.0", "chalk": "^4.1.0", "detect-package-manager": "^2.0.1", "fetch-retry": "^5.0.2", @@ -9257,9 +9243,9 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -9374,18 +9360,18 @@ } }, "node_modules/@storybook/web-components": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-7.4.6.tgz", - "integrity": "sha512-HWqkZtdkmNimkzMmE0mGRys2ee9qgEeDVCruiffySVquBRMQ6n1xgvHZYaO6OLlMeH0YHPg083ZqUasS5GsYVg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-7.5.0.tgz", + "integrity": "sha512-fkEEfssthEgvf2DUlGBfj+mtpCgiuiC8BuZqf45ueMm8iYV34EZmPNzTopPHBsm2z8VM3WZDuIFQ1puhLy5+sg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-client": "7.4.6", - "@storybook/docs-tools": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-client": "7.5.0", + "@storybook/docs-tools": "7.5.0", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/types": "7.5.0", "tiny-invariant": "^1.3.1", "ts-dedent": "^2.0.0" }, @@ -9397,19 +9383,19 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "lit": "^2.0.0" + "lit": "^2.0.0 || ^3.0.0" } }, "node_modules/@storybook/web-components-vite": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-7.4.6.tgz", - "integrity": "sha512-L/y6MTLbqfHaM0faK9Yl8n5PIyW4daZrtk7NfaOT6UjgNFjOx3o4CctYew6oj90cNk5HdZQX2OZny043GxDLZw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-7.5.0.tgz", + "integrity": "sha512-FZa5pbexnwqI0P2TFOa10vzzoABfsHBLRC0uEWntdxJYJso28xZPHdzuyNLTniOYsi+n5Eyifd3nJ+kmOS4OJg==", "dev": true, "dependencies": { - "@storybook/builder-vite": "7.4.6", - "@storybook/core-server": "7.4.6", - "@storybook/node-logger": "7.4.6", - "@storybook/web-components": "7.4.6", + "@storybook/builder-vite": "7.5.0", + "@storybook/core-server": "7.5.0", + "@storybook/node-logger": "7.5.0", + "@storybook/web-components": "7.5.0", "magic-string": "^0.30.0" }, "engines": { @@ -9424,26 +9410,14 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@storybook/web-components-vite/node_modules/magic-string": { - "version": "0.30.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", - "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@storybook/web-components/node_modules/@storybook/channels": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.6.tgz", - "integrity": "sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.5.0.tgz", + "integrity": "sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -9455,9 +9429,9 @@ } }, "node_modules/@storybook/web-components/node_modules/@storybook/client-logger": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.6.tgz", - "integrity": "sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.5.0.tgz", + "integrity": "sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -9468,9 +9442,9 @@ } }, "node_modules/@storybook/web-components/node_modules/@storybook/core-events": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.6.tgz", - "integrity": "sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.5.0.tgz", + "integrity": "sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -9481,19 +9455,19 @@ } }, "node_modules/@storybook/web-components/node_modules/@storybook/manager-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.6.tgz", - "integrity": "sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.5.0.tgz", + "integrity": "sha512-n9EaJTThsuFiBDs+GcmNBHnvLhH0znJQprhIQqHNVnosCs/7sloYUzWZzZvPwfnfPvRR7ostEEMXvriaYXYdJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.6", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", + "@storybook/router": "7.5.0", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -9512,17 +9486,17 @@ } }, "node_modules/@storybook/web-components/node_modules/@storybook/preview-api": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.6.tgz", - "integrity": "sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.5.0.tgz", + "integrity": "sha512-+DubgKwYFk532FKDB6sEGaG47wr0t137aIQSjbNwVmXXxj0QY0zIAThtERx7w6eHS7ZjOs6xlLEZhzC4FI525g==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", - "@storybook/client-logger": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/channels": "7.5.0", + "@storybook/client-logger": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.6", + "@storybook/types": "7.5.0", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -9538,12 +9512,12 @@ } }, "node_modules/@storybook/web-components/node_modules/@storybook/router": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.6.tgz", - "integrity": "sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.5.0.tgz", + "integrity": "sha512-NzPwjndmOEOUL8jK5kUrSvRUIcN5Z+h+l0Z8g4I56RoEhNTcKeOW4jbcT4WKnR9H455dti8HAcTV/4x59GpgxQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -9557,13 +9531,13 @@ } }, "node_modules/@storybook/web-components/node_modules/@storybook/theming": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.6.tgz", - "integrity": "sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.5.0.tgz", + "integrity": "sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.6", + "@storybook/client-logger": "7.5.0", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -9577,12 +9551,12 @@ } }, "node_modules/@storybook/web-components/node_modules/@storybook/types": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.6.tgz", - "integrity": "sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.5.0.tgz", + "integrity": "sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.4.6", + "@storybook/channels": "7.5.0", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -10130,18 +10104,18 @@ } }, "node_modules/@types/chart.js": { - "version": "2.9.38", - "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.38.tgz", - "integrity": "sha512-rLoHHprkVEDpAXqke/xHalyXR+5Nv+3tfViwT/UnJZ41Wp/XPaSRlJKw2PU3S3tTCqKKyjkYai+VpeHoti79XQ==", + "version": "2.9.39", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.39.tgz", + "integrity": "sha512-FAsZ6v8ds40mVCgM44T1/ZDKuT1Lh6/a2D3nD8ZNX9SqBObBc0vH2VrcLjYP3PL8onPPt9rIw+QyEkgrzj31TQ==", "dev": true, "dependencies": { "moment": "^2.10.2" } }, "node_modules/@types/codemirror": { - "version": "5.60.10", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.10.tgz", - "integrity": "sha512-ZTA3teiCWKT8HUUofqlGPlShu5ojdIajizsS0HpH6GL0/iEdjRt7fXbCLHHqKYP5k7dC/HnnWIjZAiELUwBdjQ==", + "version": "5.60.12", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.12.tgz", + "integrity": "sha512-SFSj5Tb/mtQoVgaltsipdRGG1PkcFu/L0OXPNBGCXYUQtwsNoAGRNNHOTl1jYcQUcEI77EiUfk94bgETTbSo/A==", "dev": true, "dependencies": { "@types/tern": "*" @@ -10157,9 +10131,9 @@ } }, "node_modules/@types/cross-spawn": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.3.tgz", - "integrity": "sha512-BDAkU7WHHRHnvBf5z89lcvACsvkz/n7Tv+HyD/uW76O29HoH1Tk/W6iQrepaZVbisvlEek4ygwT8IW7ow9XLAA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.4.tgz", + "integrity": "sha512-GGLpeThc2Bu8FBGmVn76ZU3lix17qZensEI4/MPty0aZpm2CHfgEMis31pf5X5EiudYKcPAsWciAsCALoPo5dw==", "dev": true, "dependencies": { "@types/node": "*" @@ -10204,15 +10178,15 @@ "dev": true }, "node_modules/@types/ejs": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.3.tgz", - "integrity": "sha512-mv5T/JI/bu+pbfz1o+TLl1NF0NIBbjS0Vl6Ppz1YY9DkXfzZT0lelXpfS5i3ZS3U/p90it7uERQpBvLYoK8e4A==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.4.tgz", + "integrity": "sha512-fnM/NjByiWdSRJRrmGxgqOSAnmOnsvX1QcNYk5TVyIIj+7ZqOKMb9gQa4OIl/lil2w/8TiTWV+nz3q8yqxez/w==", "dev": true }, "node_modules/@types/emscripten": { - "version": "1.39.8", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.8.tgz", - "integrity": "sha512-Rk0HKcMXFUuqT32k1kXHZWgxiMvsyYsmlnjp0rLKa0MMoqXLE3T9dogDBTRfuc3SAsXu97KD3k4SKR1lHqd57w==", + "version": "1.39.9", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.9.tgz", + "integrity": "sha512-ILdWj4XYtNOqxJaW22NEQx2gJsLfV5ncxYhhGX1a1H1lXl2Ta0gUz7QOnOoF1xQbJwWDjImi8gXN9mKdIf6n9g==", "dev": true }, "node_modules/@types/estree": { @@ -10271,18 +10245,18 @@ } }, "node_modules/@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", + "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/grecaptcha": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/grecaptcha/-/grecaptcha-3.0.5.tgz", - "integrity": "sha512-FyrF9WpBz34ZIqLu49kK+wL2NXgBOqeiWOinx7VW5AS01o0F1uejhfpQ6RHlm5sOyEbR7viIC7mSUzUC09NAIQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/grecaptcha/-/grecaptcha-3.0.6.tgz", + "integrity": "sha512-4BR/3v+pbiRt3cwRwibFnV4+LmuvRUjVVqgeCul9ODAyQhlPKE4tIIRJwZUeWWpmX8e9vo/xXuQTQl8FJPP7KA==", "dev": true }, "node_modules/@types/http-errors": { @@ -10292,24 +10266,24 @@ "dev": true }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==", "dev": true }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz", + "integrity": "sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz", + "integrity": "sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==", "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" @@ -10348,9 +10322,9 @@ "dev": true }, "node_modules/@types/mime-types": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.2.tgz", - "integrity": "sha512-q9QGHMGCiBJCHEvd4ZLdasdqXv570agPsUW0CeIm/B8DzhxsYMerD0l3IlI+EQ1A2RWHY2mmM9x1YIuuWxisCg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.3.tgz", + "integrity": "sha512-bvxCbHeeS7quxS7uOJShyoOQj/BfLabhF6mk9Rmr+2MRfW8W1yxyyL/0GTxLFTHen41GrIw4K3D4DrLouhb8vg==", "dev": true }, "node_modules/@types/minimatch": { @@ -10500,9 +10474,9 @@ "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==" }, "node_modules/@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", + "version": "17.0.28", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz", + "integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -10515,16 +10489,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", - "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", + "integrity": "sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/type-utils": "6.7.5", - "@typescript-eslint/utils": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/type-utils": "6.8.0", + "@typescript-eslint/utils": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -10583,15 +10557,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", - "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", + "integrity": "sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4" }, "engines": { @@ -10611,13 +10585,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", - "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz", + "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5" + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -10628,13 +10602,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", - "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz", + "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/utils": "6.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -10655,9 +10629,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", - "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz", + "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -10668,13 +10642,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", - "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz", + "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -10728,17 +10702,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", - "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz", + "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", "semver": "^7.5.4" }, "engines": { @@ -10786,12 +10760,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", - "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz", + "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/types": "6.8.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -10945,15 +10919,15 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/aggregate-error": { @@ -11845,9 +11819,9 @@ } }, "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -13544,9 +13518,9 @@ "dev": true }, "node_modules/esbuild-register": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.4.2.tgz", - "integrity": "sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz", + "integrity": "sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -14759,9 +14733,9 @@ "dev": true }, "node_modules/flow-parser": { - "version": "0.217.2", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.217.2.tgz", - "integrity": "sha512-O+nt/FLXa1hTwtW0O9h36iZjbL84G8e1uByx5dDXMC97AJEbZXwJ4ohfaE8BNWrYFyYX0NGfz1o8AtLQvaaD/Q==", + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.219.0.tgz", + "integrity": "sha512-f1RKw+2QW4HCwCQ7qw8fTrlWmQnPIHmWDYbrMhXSSAuDbQbncY63I3Y/vwgimChGF2PT4qtXusu04R3wtCh4hw==", "dev": true, "engines": { "node": ">=0.4.0" @@ -15112,18 +15086,18 @@ } }, "node_modules/giget": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.2.tgz", - "integrity": "sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.3.tgz", + "integrity": "sha512-zHuCeqtfgqgDwvXlR84UNgnJDuUHQcNI5OqWqFxxuk2BshuKbYhJWdxBsEo4PvKqoGh23lUAIvBNpChMLv7/9Q==", "dev": true, "dependencies": { - "colorette": "^2.0.19", + "colorette": "^2.0.20", "defu": "^6.1.2", - "https-proxy-agent": "^5.0.1", + "https-proxy-agent": "^7.0.2", "mri": "^1.2.0", - "node-fetch-native": "^1.0.2", - "pathe": "^1.1.0", - "tar": "^6.1.13" + "node-fetch-native": "^1.4.0", + "pathe": "^1.1.1", + "tar": "^6.2.0" }, "bin": { "giget": "dist/cli.mjs" @@ -15528,16 +15502,16 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -17197,12 +17171,12 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -19319,6 +19293,30 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/pseudolocale": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-2.0.0.tgz", + "integrity": "sha512-g1K9tCQYY4e3UGtnW8qs3kGWAOONxt7i5wuOFvf3N1EIIRhiLVIhZ9AM/ZyGTxsp231JbFywJU/EbJ5ZoqnZdg==", + "dev": true, + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "pseudolocale": "dist/cli.mjs" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pseudolocale/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -19456,9 +19454,9 @@ } }, "node_modules/pyright": { - "version": "1.1.331", - "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.331.tgz", - "integrity": "sha512-y1ZWJQ6faOoqehwSjGjDflBsaXVyJPrlrtc+pHmXnEEYfGlpt2Z9SwTJfhUqHBk7L8hPsueSwt+68A1x7Fy/TQ==", + "version": "1.1.332", + "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.332.tgz", + "integrity": "sha512-x3xuEhp6P+6UJfKF41o7f4Z9abh/LWXWF8CReSEsg/m/5IeMBtqCqPrxZnKswRjQlBlCD771QYkMatEVjeH2ZA==", "dev": true, "bin": { "pyright": "index.js", @@ -20239,9 +20237,9 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.0.2.tgz", - "integrity": "sha512-MCScu4usMPCeVFaiLcgMDaBQeYi1z6vpWxz0r0hq0Hv77Y2YuOTZldkuNJ54BdYBH3e+nkrk6j0Rre/NLDBYzg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.1.4.tgz", + "integrity": "sha512-U8Yk1lQRKqCkDBip/pMYT+IKaN7b7UesK3fLSTuHBoBJacCE+oBqo/dfG/gkUdQNNB2OBmRP98cn2C2bkYZkyw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -20251,18 +20249,18 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.0.2", - "@rollup/rollup-android-arm64": "4.0.2", - "@rollup/rollup-darwin-arm64": "4.0.2", - "@rollup/rollup-darwin-x64": "4.0.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.0.2", - "@rollup/rollup-linux-arm64-gnu": "4.0.2", - "@rollup/rollup-linux-arm64-musl": "4.0.2", - "@rollup/rollup-linux-x64-gnu": "4.0.2", - "@rollup/rollup-linux-x64-musl": "4.0.2", - "@rollup/rollup-win32-arm64-msvc": "4.0.2", - "@rollup/rollup-win32-ia32-msvc": "4.0.2", - "@rollup/rollup-win32-x64-msvc": "4.0.2", + "@rollup/rollup-android-arm-eabi": "4.1.4", + "@rollup/rollup-android-arm64": "4.1.4", + "@rollup/rollup-darwin-arm64": "4.1.4", + "@rollup/rollup-darwin-x64": "4.1.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.1.4", + "@rollup/rollup-linux-arm64-gnu": "4.1.4", + "@rollup/rollup-linux-arm64-musl": "4.1.4", + "@rollup/rollup-linux-x64-gnu": "4.1.4", + "@rollup/rollup-linux-x64-musl": "4.1.4", + "@rollup/rollup-win32-arm64-msvc": "4.1.4", + "@rollup/rollup-win32-ia32-msvc": "4.1.4", + "@rollup/rollup-win32-x64-msvc": "4.1.4", "fsevents": "~2.3.2" } }, @@ -21301,12 +21299,12 @@ "dev": true }, "node_modules/storybook": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.4.6.tgz", - "integrity": "sha512-YkFSpnR47j5zz7yElA+2axLjXN7K7TxDGJRHHlqXmG5iQ0PXzmjrj2RxMDKFz4Ybp/QjEUoJ4rx//ESEY0Nb5A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.5.0.tgz", + "integrity": "sha512-dmvQNSuoHq1KrPcK8siApBi5n5reSf6RFAlLHYD+nhM+EP6SL2fXdVjP6ZynTUMRu1NQ5YR/oJhz/SsBzJNkcA==", "dev": true, "dependencies": { - "@storybook/cli": "7.4.6" + "@storybook/cli": "7.5.0" }, "bin": { "sb": "index.js", @@ -23404,9 +23402,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", + "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==", "engines": { "node": ">= 14" } diff --git a/web/package.json b/web/package.json index 9555d20d9..ba0ad861f 100644 --- a/web/package.json +++ b/web/package.json @@ -21,6 +21,9 @@ "precommit": "run-s tsc lit-analyse lint:precommit lint:spelling prettier", "prettier-check": "prettier --check .", "prettier": "prettier --write .", + "pseudolocalize:build-extract-script": "cd scripts && tsc --esModuleInterop --module es2020 --moduleResolution 'node' pseudolocalize.ts && mv pseudolocalize.js pseudolocalize.mjs", + "pseudolocalize:extract": "node scripts/pseudolocalize.mjs", + "pseudolocalize": "run-s pseudolocalize:build-extract-script pseudolocalize:extract", "tsc:execute": "tsc --noEmit -p .", "tsc": "run-s build-locales tsc:execute", "storybook": "storybook dev -p 6006", @@ -33,17 +36,17 @@ "@codemirror/lang-xml": "^6.0.2", "@codemirror/legacy-modes": "^6.3.3", "@codemirror/theme-one-dark": "^6.1.2", - "@formatjs/intl-listformat": "^7.4.2", + "@formatjs/intl-listformat": "^7.5.0", "@fortawesome/fontawesome-free": "^6.4.2", - "@goauthentik/api": "^2023.8.3-1696847703", + "@goauthentik/api": "^2023.8.3-1697651039", "@lit-labs/context": "^0.4.1", "@lit-labs/task": "^3.1.0", "@lit/localize": "^0.11.4", "@open-wc/lit-helpers": "^0.6.0", "@patternfly/elements": "^2.4.0", "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.73.0", - "@sentry/tracing": "^7.73.0", + "@sentry/browser": "^7.74.1", + "@sentry/tracing": "^7.74.1", "@webcomponents/webcomponentsjs": "^2.8.0", "base64-js": "^1.5.1", "chart.js": "^4.4.0", @@ -58,7 +61,7 @@ "rapidoc": "^9.3.4", "style-mod": "^4.1.0", "webcomponent-qr-code": "^1.2.0", - "yaml": "^2.3.2" + "yaml": "^2.3.3" }, "devDependencies": { "@babel/core": "^7.23.2", @@ -74,22 +77,22 @@ "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@lit/localize-tools": "^0.7.0", "@rollup/plugin-babel": "^6.0.4", - "@rollup/plugin-commonjs": "^25.0.5", + "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.3", + "@rollup/plugin-replace": "^5.0.4", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", - "@storybook/addon-essentials": "^7.4.6", - "@storybook/addon-links": "^7.4.6", + "@storybook/addon-essentials": "^7.5.0", + "@storybook/addon-links": "^7.5.0", "@storybook/blocks": "^7.1.1", - "@storybook/web-components": "^7.4.6", - "@storybook/web-components-vite": "^7.4.6", + "@storybook/web-components": "^7.5.0", + "@storybook/web-components-vite": "^7.5.0", "@trivago/prettier-plugin-sort-imports": "^4.2.0", - "@types/chart.js": "^2.9.38", - "@types/codemirror": "5.60.10", - "@types/grecaptcha": "^3.0.5", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", + "@types/chart.js": "^2.9.39", + "@types/codemirror": "5.60.12", + "@types/grecaptcha": "^3.0.6", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", "babel-plugin-macros": "^3.1.0", "babel-plugin-tsconfig-paths": "^1.0.3", "cross-env": "^7.0.3", @@ -102,14 +105,15 @@ "lit-analyzer": "^1.2.1", "npm-run-all": "^4.1.5", "prettier": "^3.0.3", - "pyright": "^1.1.331", + "pseudolocale": "^2.0.0", + "pyright": "^1.1.332", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.0.2", + "rollup": "^4.1.4", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-postcss-lit": "^2.1.0", - "storybook": "^7.4.6", + "storybook": "^7.5.0", "storybook-addon-mock": "^4.3.0", "ts-lit-plugin": "^1.2.1", "tslib": "^2.6.2", @@ -118,8 +122,8 @@ "vite-tsconfig-paths": "^4.2.1" }, "optionalDependencies": { - "@esbuild/darwin-arm64": "^0.19.4", + "@esbuild/darwin-arm64": "^0.19.5", "@esbuild/linux-amd64": "^0.18.11", - "@esbuild/linux-arm64": "^0.19.4" + "@esbuild/linux-arm64": "^0.19.5" } } diff --git a/web/scripts/pseudolocalize.ts b/web/scripts/pseudolocalize.ts new file mode 100644 index 000000000..308632ff9 --- /dev/null +++ b/web/scripts/pseudolocalize.ts @@ -0,0 +1,47 @@ +import { readFileSync } from "fs"; +import path from "path"; +import pseudolocale from "pseudolocale"; +import { fileURLToPath } from "url"; + +import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js"; +import type { Message, ProgramMessage } from "@lit/localize-tools/lib/messages.d.ts"; +import { sortProgramMessages } from "@lit/localize-tools/lib/messages.js"; +import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.js"; +import type { Config } from "@lit/localize-tools/lib/types/config.d.ts"; +import type { Locale } from "@lit/localize-tools/lib/types/locale.d.ts"; +import type { TransformOutputConfig } from "@lit/localize-tools/lib/types/modes.d.ts"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); +const pseudoLocale: Locale = "pseudo-LOCALE" as Locale; +const targetLocales: Locale[] = [pseudoLocale]; +const baseConfig = JSON.parse(readFileSync(path.join(__dirname, "../lit-localize.json"), "utf-8")); + +// Need to make some internal specifications to satisfy the transformer. It doesn't actually matter +// which Localizer we use (transformer or runtime), because all of the functionality we care about +// is in their common parent class, but I had to pick one. Everything else here is just pure +// exploitation of the lit/localize-tools internals. + +const config: Config = { + ...baseConfig, + baseDir: path.join(__dirname, ".."), + targetLocales, + output: { + ...baseConfig, + mode: "transform", + }, + resolve: (path: string) => path, +} as Config; + +const pseudoMessagify = (message: ProgramMessage) => ({ + name: message.name, + contents: message.contents.map((content) => + typeof content === "string" ? pseudolocale(content, { prepend: "", append: "" }) : content, + ), +}); + +const localizer = new TransformLitLocalizer(config as Config & { output: TransformOutputConfig }); +const { messages } = localizer.extractSourceMessages(); +const translations = messages.map(pseudoMessagify); +const sorted = sortProgramMessages([...messages]); +const formatter = makeFormatter(config); +formatter.writeOutput(sorted, new Map([[pseudoLocale, translations]])); diff --git a/web/src/admin/AdminInterface.ts b/web/src/admin/AdminInterface.ts index fa6d4efa5..44d927ae2 100644 --- a/web/src/admin/AdminInterface.ts +++ b/web/src/admin/AdminInterface.ts @@ -116,7 +116,11 @@ export class AdminInterface extends Interface { configureSentry(true); this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve(); this.user = await me(); - if (!this.user.user.isSuperuser && this.user.user.pk > 0) { + const canAccessAdmin = + this.user.user.isSuperuser || + // TODO: somehow add `access_admin_interface` to the API schema + this.user.user.systemPermissions.includes("access_admin_interface"); + if (!canAccessAdmin && this.user.user.pk > 0) { window.location.assign("/if/user"); } } @@ -211,6 +215,7 @@ export class AdminInterface extends Interface { [null, msg("Directory"), null, [ ["/identity/users", msg("Users"), [`^/identity/users/(?${ID_REGEX})$`]], ["/identity/groups", msg("Groups"), [`^/identity/groups/(?${UUID_REGEX})$`]], + ["/identity/roles", msg("Roles"), [`^/identity/roles/(?${UUID_REGEX})$`]], ["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?${SLUG_REGEX})$`]], ["/core/tokens", msg("Tokens and App passwords")], ["/flow/stages/invitations", msg("Invitations")]]], diff --git a/web/src/admin/Routes.ts b/web/src/admin/Routes.ts index 55a830835..1c7a9739e 100644 --- a/web/src/admin/Routes.ts +++ b/web/src/admin/Routes.ts @@ -80,6 +80,14 @@ export const ROUTES: Route[] = [ await import("@goauthentik/admin/users/UserViewPage"); return html``; }), + new Route(new RegExp("^/identity/roles$"), async () => { + await import("@goauthentik/admin/roles/RoleListPage"); + return html``; + }), + new Route(new RegExp(`^/identity/roles/(?${UUID_REGEX})$`), async (args) => { + await import("@goauthentik/admin/roles/RoleViewPage"); + return html``; + }), new Route(new RegExp("^/flow/stages/invitations$"), async () => { await import("@goauthentik/admin/stages/invitation/InvitationListPage"); return html``; diff --git a/web/src/admin/admin-overview/cards/AdminStatusCard.ts b/web/src/admin/admin-overview/cards/AdminStatusCard.ts index b773a78bc..d20f5d570 100644 --- a/web/src/admin/admin-overview/cards/AdminStatusCard.ts +++ b/web/src/admin/admin-overview/cards/AdminStatusCard.ts @@ -2,9 +2,12 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { PFSize } from "@goauthentik/elements/Spinner"; import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard"; +import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { until } from "lit/directives/until.js"; +import { ResponseError } from "@goauthentik/api"; + export interface AdminStatus { icon: string; message?: TemplateResult; @@ -41,6 +44,12 @@ export abstract class AdminStatusCard extends AggregateCard { ${status.message ? html`

${status.message}

` : html``}`; + }) + .catch((exc: ResponseError) => { + return html`

+  ${exc.response.statusText} +

+

${msg("Failed to fetch")}

`; }), html``, )} diff --git a/web/src/admin/applications/ApplicationForm.ts b/web/src/admin/applications/ApplicationForm.ts index e6f5fae93..42489f9f3 100644 --- a/web/src/admin/applications/ApplicationForm.ts +++ b/web/src/admin/applications/ApplicationForm.ts @@ -1,7 +1,12 @@ import "@goauthentik/admin/applications/ProviderSelectModal"; import { iconHelperText } from "@goauthentik/admin/helperText"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; -import { first, groupBy } from "@goauthentik/common/utils"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-file-input"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/components/ak-textarea-input"; import { rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -23,12 +28,34 @@ import { CoreApi, PolicyEngineMode, Provider, - ProvidersAllListRequest, - ProvidersApi, } from "@goauthentik/api"; +import "./components/ak-backchannel-input"; +import "./components/ak-provider-search-input"; + +export const policyOptions = [ + { + label: "any", + value: PolicyEngineMode.Any, + default: true, + description: html`${msg("Any policy must match to grant access")}`, + }, + { + label: "all", + value: PolicyEngineMode.All, + description: html`${msg("All policies must match to grant access")}`, + }, +]; + @customElement("ak-application-form") export class ApplicationForm extends ModelForm { + constructor() { + super(); + this.handleConfirmBackchannelProviders = this.handleConfirmBackchannelProviders.bind(this); + this.makeRemoveBackchannelProviderHandler = + this.makeRemoveBackchannelProviderHandler.bind(this); + } + async loadInstance(pk: string): Promise { const app = await new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({ slug: pk, @@ -89,237 +116,137 @@ export class ApplicationForm extends ModelForm { return app; } + handleConfirmBackchannelProviders({ items }: { items: Provider[] }) { + this.backchannelProviders = items; + this.requestUpdate(); + return Promise.resolve(); + } + + makeRemoveBackchannelProviderHandler(provider: Provider) { + return () => { + const idx = this.backchannelProviders.indexOf(provider); + this.backchannelProviders.splice(idx, 1); + this.requestUpdate(); + }; + } + + handleClearIcon(ev: Event) { + ev.stopPropagation(); + if (!(ev instanceof InputEvent) || !ev.target) { + return; + } + this.clearIcon = !!(ev.target as HTMLInputElement).checked; + } + renderForm(): TemplateResult { - return html` - -

${msg("Application's display Name.")}

-
- - -

- ${msg("Internal application name, used in URLs.")} -

-
- - -

- ${msg( - "Optionally enter a group name. Applications with identical groups are shown grouped together.", - )} -

-
- - => { - const args: ProvidersAllListRequest = { - ordering: "name", - }; - if (query !== undefined) { - args.search = query; - } - const items = await new ProvidersApi(DEFAULT_CONFIG).providersAllList(args); - return items.results; - }} - .renderElement=${(item: Provider): string => { - return item.name; - }} - .value=${(item: Provider | undefined): number | undefined => { - return item?.pk; - }} - .groupBy=${(items: Provider[]) => { - return groupBy(items, (item) => item.verboseName); - }} - .selected=${(item: Provider): boolean => { - return this.instance?.provider === item.pk; - }} - ?blankable=${true} - > - -

- ${msg("Select a provider that this application should use.")} -

-
- - + + + + + `} > -
- { - this.backchannelProviders = items; - this.requestUpdate(); - return Promise.resolve(); - }} - > - - -
- - ${this.backchannelProviders.map((provider) => { - return html` { - const idx = this.backchannelProviders.indexOf(provider); - this.backchannelProviders.splice(idx, 1); - this.requestUpdate(); - }} - > - ${provider.name} - `; - })} - -
-
-

- ${msg( - "Select backchannel providers which augment the functionality of the main provider.", - )} -

-
- - + - - - + .options=${policyOptions} + .value=${this.instance?.policyEngineMode} + > ${msg("UI settings")}
- - -

- ${msg( - "If left empty, authentik will try to extract the launch URL based on the selected provider.", - )} -

-
- - -

- ${msg( - "If checked, the launch URL will open in a new browser tab or window from the user's application library.", - )} -

-
+ + + ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) - ? html` - - ${this.instance?.metaIcon - ? html` -

- ${msg("Currently set to:")} - ${this.instance?.metaIcon} -

- ` - : html``} -
+ ? html` ${this.instance?.metaIcon ? html` - - -

- ${msg("Delete currently set icon.")} -

-
+ ` : html``}` - : html` - -

${iconHelperText}

-
`} - - - - - - + : html` + `} + +
-
`; + + `; } } diff --git a/web/src/admin/applications/ApplicationListPage.ts b/web/src/admin/applications/ApplicationListPage.ts index b29ac0df5..c06236750 100644 --- a/web/src/admin/applications/ApplicationListPage.ts +++ b/web/src/admin/applications/ApplicationListPage.ts @@ -1,5 +1,4 @@ import "@goauthentik/admin/applications/ApplicationForm"; -import "@goauthentik/admin/applications/wizard/ApplicationWizard"; import { PFSize } from "@goauthentik/app/elements/Spinner"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; @@ -10,6 +9,7 @@ import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; +// import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -23,6 +23,8 @@ import PFCard from "@patternfly/patternfly/components/Card/card.css"; import { Application, CoreApi } from "@goauthentik/api"; +import "./ApplicationWizardHint"; + @customElement("ak-application-list") export class ApplicationListPage extends TablePage { searchEnabled(): boolean { @@ -33,7 +35,7 @@ export class ApplicationListPage extends TablePage { } pageDescription(): string { return msg( - "External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.", + "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.", ); } pageIcon(): string { @@ -87,20 +89,27 @@ export class ApplicationListPage extends TablePage { ]; } + renderSectionBefore(): TemplateResult { + return html``; + } + renderSidebarAfter(): TemplateResult { // 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 - return html` -
-
-
- -
+ >*/ + + return html`
+
+
+
-
`; +
+
`; } renderToolbarSelected(): TemplateResult { diff --git a/web/src/admin/applications/ApplicationViewPage.ts b/web/src/admin/applications/ApplicationViewPage.ts index f65182ca7..84ea2f207 100644 --- a/web/src/admin/applications/ApplicationViewPage.ts +++ b/web/src/admin/applications/ApplicationViewPage.ts @@ -3,6 +3,7 @@ import "@goauthentik/admin/applications/ApplicationCheckAccessForm"; import "@goauthentik/admin/applications/ApplicationForm"; import "@goauthentik/admin/policies/BoundPoliciesList"; import { PFSize } from "@goauthentik/app/elements/Spinner"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/components/ak-app-icon"; import "@goauthentik/components/events/ObjectChangelog"; @@ -27,7 +28,12 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Application, CoreApi, OutpostsApi } from "@goauthentik/api"; +import { + Application, + CoreApi, + OutpostsApi, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; @customElement("ak-application-view") export class ApplicationViewPage extends AKElement { @@ -299,6 +305,12 @@ export class ApplicationViewPage extends AKElement {
+ `; } } diff --git a/web/src/admin/applications/ApplicationWizardHint.ts b/web/src/admin/applications/ApplicationWizardHint.ts index 9f0a22e88..288e18dbd 100644 --- a/web/src/admin/applications/ApplicationWizardHint.ts +++ b/web/src/admin/applications/ApplicationWizardHint.ts @@ -1,4 +1,4 @@ -import { MessageLevel } from "@goauthentik/common/messages"; +import "@goauthentik/admin/applications/wizard/ak-application-wizard"; import { ShowHintController, ShowHintControllerHost, @@ -6,18 +6,37 @@ import { import "@goauthentik/components/ak-hint/ak-hint"; import "@goauthentik/components/ak-hint/ak-hint-body"; import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/Label"; import "@goauthentik/elements/buttons/ActionButton/ak-action-button"; -import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; +import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; -import { html, nothing } from "lit"; +import { msg } from "@lit/localize"; +import { html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; +import { styleMap } from "lit/directives/style-map.js"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFLabel from "@patternfly/patternfly/components/Label/label.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; +const closeButtonIcon = html``; + @customElement("ak-application-wizard-hint") export class AkApplicationWizardHint extends AKElement implements ShowHintControllerHost { static get styles() { - return [PFPage]; + return [PFButton, PFPage, PFLabel]; } @property({ type: Boolean, attribute: "show-hint" }) @@ -36,33 +55,60 @@ export class AkApplicationWizardHint extends AKElement implements ShowHintContro ); } + renderReminder() { + const sectionStyles = { + paddingBottom: "0", + marginBottom: "-0.5rem", + marginRight: "0.0625rem", + textAlign: "right", + }; + const textStyle = { maxWidth: "60ch" }; + + return html`
+ + + + ${msg("One hint, 'New Application Wizard', is currently hidden")} + + + + +
`; + } + renderHint() { return html`

- Authentik has a new Application Wizard that can configure both an - application and its authentication provider at the same time. - Learn more about the wizard here. + You can now configure both an application and its authentication provider at + the same time with our new Application Wizard. +

- { - showMessage({ - message: "This would have shown the wizard", - level: MessageLevel.success, - }); - }} - >Create with Wizard
+ + + ${this.showHintController.render()}
`; } render() { - return this.showHint || this.forceHint ? this.renderHint() : nothing; + return this.showHint || this.forceHint ? this.renderHint() : this.renderReminder(); } } diff --git a/web/src/admin/applications/components/ak-backchannel-input.ts b/web/src/admin/applications/components/ak-backchannel-input.ts new file mode 100644 index 000000000..2130ba10b --- /dev/null +++ b/web/src/admin/applications/components/ak-backchannel-input.ts @@ -0,0 +1,80 @@ +import "@goauthentik/admin/applications/ProviderSelectModal"; +import { AKElement } from "@goauthentik/elements/Base"; + +import { TemplateResult, 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 { Provider } from "@goauthentik/api"; + +@customElement("ak-backchannel-providers-input") +export class AkBackchannelProvidersInput 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. + // + // This field is so highly specialized that it would make more sense if we put the API and the + // fetcher here. + // + // 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: Array }) + providers: Provider[] = []; + + @property({ type: Object }) + tooltip?: TemplateResult; + + @property({ attribute: false, type: Object }) + confirm!: ({ items }: { items: Provider[] }) => Promise; + + @property({ attribute: false, type: Object }) + remover!: (provider: Provider) => () => void; + + @property({ type: String }) + value = ""; + + @property({ type: Boolean }) + required = false; + + @property({ type: String }) + help = ""; + + render() { + const renderOneChip = (provider: Provider) => + html`${provider.name}`; + + return html` + +
+ + + +
+ ${map(this.providers, renderOneChip)} +
+
+ ${this.help ? html`

${this.help}

` : nothing} +
+ `; + } +} diff --git a/web/src/admin/applications/components/ak-provider-search-input.ts b/web/src/admin/applications/components/ak-provider-search-input.ts new file mode 100644 index 000000000..552cb0764 --- /dev/null +++ b/web/src/admin/applications/components/ak-provider-search-input.ts @@ -0,0 +1,80 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { groupBy } from "@goauthentik/common/utils"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/forms/SearchSelect"; + +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { Provider, ProvidersAllListRequest, ProvidersApi } from "@goauthentik/api"; + +const renderElement = (item: Provider) => item.name; +const renderValue = (item: Provider | undefined) => item?.pk; +const doGroupBy = (items: Provider[]) => groupBy(items, (item) => item.verboseName); + +async function fetch(query?: string) { + const args: ProvidersAllListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const items = await new ProvidersApi(DEFAULT_CONFIG).providersAllList(args); + return items.results; +} + +@customElement("ak-provider-search-input") +export class AkProviderInput 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: Number }) + value?: number; + + @property({ type: Boolean }) + required = false; + + @property({ type: Boolean }) + blankable = false; + + @property({ type: String }) + help = ""; + + constructor() { + super(); + this.selected = this.selected.bind(this); + } + + selected(item: Provider) { + return this.value !== undefined && this.value === item.pk; + } + + render() { + return html` + + + ${this.help ? html`

${this.help}

` : nothing} +
`; + } +} diff --git a/web/src/admin/applications/wizard/ApplicationWizard.ts b/web/src/admin/applications/wizard/ApplicationWizard.ts deleted file mode 100644 index d8ceb9749..000000000 --- a/web/src/admin/applications/wizard/ApplicationWizard.ts +++ /dev/null @@ -1,64 +0,0 @@ -import "@goauthentik/admin/applications/wizard/InitialApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/TypeApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/ldap/TypeLDAPApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/link/TypeLinkApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthAPIApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthImplicitApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/proxy/TypeProxyApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/saml/TypeSAMLApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/saml/TypeSAMLConfigApplicationWizardPage"; -import "@goauthentik/admin/applications/wizard/saml/TypeSAMLImportApplicationWizardPage"; -import { AKElement } from "@goauthentik/elements/Base"; -import "@goauthentik/elements/wizard/Wizard"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -@customElement("ak-application-wizard") -export class ApplicationWizard extends AKElement { - static get styles(): CSSResult[] { - return [PFBase, PFButton, PFRadio]; - } - - @property({ type: Boolean }) - open = false; - - @property() - createText = msg("Create"); - - @property({ type: Boolean }) - showButton = true; - - @property({ attribute: false }) - finalHandler: () => Promise = () => { - return Promise.resolve(); - }; - - render(): TemplateResult { - return html` - { - return this.finalHandler(); - }} - > - ${this.showButton - ? html`` - : html``} - - `; - } -} diff --git a/web/src/admin/applications/wizard/BasePanel.css.ts b/web/src/admin/applications/wizard/BasePanel.css.ts new file mode 100644 index 000000000..31479ed80 --- /dev/null +++ b/web/src/admin/applications/wizard/BasePanel.css.ts @@ -0,0 +1,28 @@ +import { css } from "lit"; + +import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css"; +import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; +import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +export const styles = [ + PFBase, + PFCard, + PFButton, + PFForm, + PFAlert, + PFRadio, + PFInputGroup, + PFFormControl, + PFSwitch, + css` + select[multiple] { + height: 15em; + } + `, +]; diff --git a/web/src/admin/applications/wizard/BasePanel.ts b/web/src/admin/applications/wizard/BasePanel.ts new file mode 100644 index 000000000..a395fc4b3 --- /dev/null +++ b/web/src/admin/applications/wizard/BasePanel.ts @@ -0,0 +1,35 @@ +import { WizardPanel } from "@goauthentik/components/ak-wizard-main/types"; +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { consume } from "@lit-labs/context"; +import { query } from "@lit/reactive-element/decorators.js"; + +import { styles as AwadStyles } from "./BasePanel.css"; + +import { applicationWizardContext } from "./ContextIdentity"; +import type { ApplicationWizardState, ApplicationWizardStateUpdate } from "./types"; + +export class ApplicationWizardPageBase + extends CustomEmitterElement(AKElement) + implements WizardPanel +{ + static get styles() { + return AwadStyles; + } + + @query("form") + form!: HTMLFormElement; + + rendered = false; + + @consume({ context: applicationWizardContext }) + public wizard!: ApplicationWizardState; + + // This used to be more complex; now it just establishes the event name. + dispatchWizardUpdate(update: ApplicationWizardStateUpdate) { + this.dispatchCustomEvent("ak-wizard-update", update); + } +} + +export default ApplicationWizardPageBase; diff --git a/web/src/admin/applications/wizard/ContextIdentity.ts b/web/src/admin/applications/wizard/ContextIdentity.ts new file mode 100644 index 000000000..f03f147a6 --- /dev/null +++ b/web/src/admin/applications/wizard/ContextIdentity.ts @@ -0,0 +1,9 @@ +import { createContext } from "@lit-labs/context"; + +import { ApplicationWizardState } from "./types"; + +export const applicationWizardContext = createContext( + Symbol("ak-application-wizard-state-context"), +); + +export default applicationWizardContext; diff --git a/web/src/admin/applications/wizard/InitialApplicationWizardPage.ts b/web/src/admin/applications/wizard/InitialApplicationWizardPage.ts deleted file mode 100644 index 1e4388e70..000000000 --- a/web/src/admin/applications/wizard/InitialApplicationWizardPage.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { convertToSlug } from "@goauthentik/common/utils"; -import { KeyUnknown } from "@goauthentik/elements/forms/Form"; -import "@goauthentik/elements/forms/FormGroup"; -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -import { ApplicationRequest, CoreApi, Provider } from "@goauthentik/api"; - -@customElement("ak-application-wizard-initial") -export class InitialApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("Application details"); - - nextDataCallback = async (data: KeyUnknown): Promise => { - const name = data.name as string; - let slug = convertToSlug(name || ""); - // Check if an application with the generated slug already exists - const apps = await new CoreApi(DEFAULT_CONFIG).coreApplicationsList({ - search: slug, - }); - if (apps.results.filter((app) => app.slug == slug)) { - slug += "-1"; - } - this.host.state["slug"] = slug; - this.host.state["name"] = name; - this.host.addActionBefore(msg("Create application"), async (): Promise => { - const req: ApplicationRequest = { - name: name || "", - slug: slug, - metaPublisher: data.metaPublisher as string, - metaDescription: data.metaDescription as string, - }; - if ("provider" in this.host.state) { - req.provider = (this.host.state["provider"] as Provider).pk; - } - if ("link" in this.host.state) { - req.metaLaunchUrl = this.host.state["link"] as string; - } - this.host.state["app"] = await new CoreApi(DEFAULT_CONFIG).coreApplicationsCreate({ - applicationRequest: req, - }); - return true; - }); - return true; - }; - - renderForm(): TemplateResult { - return html` -
- - -

${msg("Application's display Name.")}

-
- - ${msg("Additional UI settings")} -
- - - - - - -
-
-
- `; - } -} diff --git a/web/src/admin/applications/wizard/TypeApplicationWizardPage.ts b/web/src/admin/applications/wizard/TypeApplicationWizardPage.ts deleted file mode 100644 index ff55cb67c..000000000 --- a/web/src/admin/applications/wizard/TypeApplicationWizardPage.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -import { TypeCreate } from "@goauthentik/api"; - -@customElement("ak-application-wizard-type") -export class TypeApplicationWizardPage extends WizardPage { - applicationTypes: TypeCreate[] = [ - { - component: "ak-application-wizard-type-oauth", - name: msg("OAuth2/OIDC"), - description: msg("Modern applications, APIs and Single-page applications."), - modelName: "", - }, - { - component: "ak-application-wizard-type-saml", - name: msg("SAML"), - description: msg( - "XML-based SSO standard. Use this if your application only supports SAML.", - ), - modelName: "", - }, - { - component: "ak-application-wizard-type-proxy", - name: msg("Proxy"), - description: msg("Legacy applications which don't natively support SSO."), - modelName: "", - }, - { - component: "ak-application-wizard-type-ldap", - name: msg("LDAP"), - description: msg( - "Provide an LDAP interface for applications and users to authenticate against.", - ), - modelName: "", - }, - { - component: "ak-application-wizard-type-link", - name: msg("Link"), - description: msg( - "Provide an LDAP interface for applications and users to authenticate against.", - ), - modelName: "", - }, - ]; - - sidebarLabel = () => msg("Authentication method"); - - static get styles(): CSSResult[] { - return [PFBase, PFButton, PFForm, PFRadio]; - } - - render(): TemplateResult { - return html`
- ${this.applicationTypes.map((type) => { - return html`
- { - this.host.steps = [ - "ak-application-wizard-initial", - "ak-application-wizard-type", - type.component, - ]; - this.host.isValid = true; - }} - /> - - ${type.description} -
`; - })} -
`; - } -} diff --git a/web/src/admin/applications/wizard/ak-application-wizard.ts b/web/src/admin/applications/wizard/ak-application-wizard.ts new file mode 100644 index 000000000..d15570f13 --- /dev/null +++ b/web/src/admin/applications/wizard/ak-application-wizard.ts @@ -0,0 +1,124 @@ +import { merge } from "@goauthentik/common/merge"; +import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard"; +import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { ContextProvider } from "@lit-labs/context"; +import { msg } from "@lit/localize"; +import { customElement, state } from "lit/decorators.js"; + +import applicationWizardContext from "./ContextIdentity"; +import { newSteps } from "./steps"; +import { + ApplicationStep, + ApplicationWizardState, + ApplicationWizardStateUpdate, + OneOfProvider, +} from "./types"; + +const freshWizardState = () => ({ + providerModel: "", + app: {}, + provider: {}, +}); + +@customElement("ak-application-wizard") +export class ApplicationWizard extends CustomListenerElement( + AkWizard, +) { + constructor() { + super(msg("Create With Wizard"), msg("New application"), msg("Create a new application")); + this.steps = newSteps(); + } + + /** + * We're going to be managing the content of the forms by percolating all of the data up to this + * class, which will ultimately transmit all of it to the server as a transaction. The + * WizardFramework doesn't know anything about the nature of the data itself; it just forwards + * valid updates to us. So here we maintain a state object *and* update it so all child + * components can access the wizard state. + * + */ + @state() + wizardState: ApplicationWizardState = freshWizardState(); + + wizardStateProvider = new ContextProvider(this, { + context: applicationWizardContext, + initialValue: this.wizardState, + }); + + /** + * One of our steps has multiple display variants, one for each type of service provider. We + * want to *preserve* a customer's decisions about different providers; never make someone "go + * back and type it all back in," even if it's probably rare that someone will chose one + * provider, realize it's the wrong one, and go back to chose a different one, *and then go + * back*. Nonetheless, strive to *never* lose customer input. + * + */ + providerCache: Map = 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... + handleUpdate(detail: ApplicationWizardStateUpdate) { + if (detail.status === "submitted") { + this.step.valid = true; + this.requestUpdate(); + return; + } + + this.step.valid = this.step.valid || detail.status === "valid"; + + const update = detail.update; + if (!update) { + return; + } + + if (this.maybeProviderSwap(update.providerModel)) { + this.requestUpdate(); + } + + this.wizardState = merge(this.wizardState, update) as ApplicationWizardState; + this.wizardStateProvider.setValue(this.wizardState); + this.requestUpdate(); + } + + close() { + this.steps = newSteps(); + this.currentStep = 0; + this.wizardState = freshWizardState(); + this.providerCache = new Map(); + this.wizardStateProvider.setValue(this.wizardState); + this.frame.value!.open = false; + } + + handleNav(stepId: number | undefined) { + if (stepId === undefined || this.steps[stepId] === undefined) { + throw new Error(`Attempt to navigate to undefined step: ${stepId}`); + } + if (stepId > this.currentStep && !this.step.valid) { + return; + } + this.currentStep = stepId; + this.requestUpdate(); + } +} diff --git a/web/src/admin/applications/wizard/application/ak-application-wizard-application-details.ts b/web/src/admin/applications/wizard/application/ak-application-wizard-application-details.ts new file mode 100644 index 000000000..13e439d76 --- /dev/null +++ b/web/src/admin/applications/wizard/application/ak-application-wizard-application-details.ts @@ -0,0 +1,101 @@ +import { policyOptions } from "@goauthentik/admin/applications/ApplicationForm"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-slug-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; +import { TemplateResult, html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import BasePanel from "../BasePanel"; + +@customElement("ak-application-wizard-application-details") +export class ApplicationWizardApplicationDetails extends BasePanel { + handleChange(ev: Event) { + if (!ev.target) { + console.warn(`Received event with no target: ${ev}`); + return; + } + + const target = ev.target as HTMLInputElement; + const value = target.type === "checkbox" ? target.checked : target.value; + this.dispatchWizardUpdate({ + update: { + app: { + [target.name]: value, + }, + }, + status: this.form.checkValidity() ? "valid" : "invalid", + }); + } + + validator() { + return this.form.reportValidity(); + } + + render(): TemplateResult { + return html`
+ + + + + + ${msg("UI Settings")} +
+ + + +
+
+
`; + } +} + +export default ApplicationWizardApplicationDetails; diff --git a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts new file mode 100644 index 000000000..16b3ce01a --- /dev/null +++ b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts @@ -0,0 +1,161 @@ +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; + +import type { ProviderModelEnum as ProviderModelEnumType, TypeCreate } from "@goauthentik/api"; +import { ProviderModelEnum, ProxyMode } from "@goauthentik/api"; +import type { + LDAPProviderRequest, + ModelRequest, + OAuth2ProviderRequest, + ProxyProviderRequest, + RadiusProviderRequest, + SAMLProviderRequest, + SCIMProviderRequest, +} from "@goauthentik/api"; + +import { OneOfProvider } from "../types"; + +type ProviderRenderer = () => TemplateResult; + +type ModelConverter = (provider: OneOfProvider) => ModelRequest; + +type ProviderType = [ + string, + string, + string, + ProviderRenderer, + ProviderModelEnumType, + ModelConverter, +]; + +export type LocalTypeCreate = TypeCreate & { + formName: string; + modelName: ProviderModelEnumType; + converter: ModelConverter; +}; + +// prettier-ignore +const _providerModelsTable: ProviderType[] = [ + [ + "oauth2provider", + msg("OAuth2/OpenID"), + msg("Modern applications, APIs and Single-page applications."), + () => html``, + ProviderModelEnum.Oauth2Oauth2provider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.Oauth2Oauth2provider, + ...(provider as OAuth2ProviderRequest), + }), + ], + [ + "ldapprovider", + msg("LDAP"), + msg("Provide an LDAP interface for applications and users to authenticate against."), + () => html``, + ProviderModelEnum.LdapLdapprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.LdapLdapprovider, + ...(provider as LDAPProviderRequest), + }), + + ], + [ + "proxyprovider-proxy", + msg("Transparent Reverse Proxy"), + msg("For transparent reverse proxies with required authentication"), + () => html``, + ProviderModelEnum.ProxyProxyprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.ProxyProxyprovider, + ...(provider as ProxyProviderRequest), + mode: ProxyMode.Proxy, + }), + ], + [ + "proxyprovider-forwardsingle", + msg("Forward Auth Single Application"), + msg("For nginx's auth_request or traefix's forwardAuth"), + () => html``, + ProviderModelEnum.ProxyProxyprovider , + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.ProxyProxyprovider, + ...(provider as ProxyProviderRequest), + mode: ProxyMode.ForwardSingle, + }), + + ], + [ + "proxyprovider-forwarddomain", + msg("Forward Auth Domain Level"), + msg("For nginx's auth_request or traefix's forwardAuth per root domain"), + () => html``, + ProviderModelEnum.ProxyProxyprovider , + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.ProxyProxyprovider, + ...(provider as ProxyProviderRequest), + mode: ProxyMode.ForwardDomain, + }), + ], + [ + "samlprovider", + msg("SAML Configuration"), + msg("Configure SAML provider manually"), + () => html``, + ProviderModelEnum.SamlSamlprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.SamlSamlprovider, + ...(provider as SAMLProviderRequest), + }), + + ], + [ + "radiusprovider", + msg("RADIUS Configuration"), + msg("Configure RADIUS provider manually"), + () => html``, + ProviderModelEnum.RadiusRadiusprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.RadiusRadiusprovider, + ...(provider as RadiusProviderRequest), + }), + + ], + [ + "scimprovider", + msg("SCIM configuration"), + msg("Configure SCIM provider manually"), + () => html``, + ProviderModelEnum.ScimScimprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.ScimScimprovider, + ...(provider as SCIMProviderRequest), + }), + + ], +]; + +function mapProviders([ + formName, + name, + description, + _, + modelName, + converter, +]: ProviderType): LocalTypeCreate { + return { + formName, + name, + description, + component: "", + modelName, + converter, + }; +} + +export const providerModelsList = _providerModelsTable.map(mapProviders); + +export const providerRendererList = new Map( + _providerModelsTable.map(([modelName, _0, _1, renderer]) => [modelName, renderer]), +); + +export default providerModelsList; diff --git a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts new file mode 100644 index 000000000..593406d66 --- /dev/null +++ b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts @@ -0,0 +1,68 @@ +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/FormGroup"; +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 { map } from "lit/directives/map.js"; + +import BasePanel from "../BasePanel"; +import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices"; +import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices"; + +@customElement("ak-application-wizard-authentication-method-choice") +export class ApplicationWizardAuthenticationMethodChoice extends BasePanel { + constructor() { + super(); + this.handleChoice = this.handleChoice.bind(this); + this.renderProvider = this.renderProvider.bind(this); + } + + handleChoice(ev: InputEvent) { + const target = ev.target as HTMLInputElement; + this.dispatchWizardUpdate({ + update: { providerModel: target.value }, + status: this.validator() ? "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) { + const method = this.wizard.providerModel; + + return html`
+ + + ${type.description} +
`; + } + + render() { + return providerModelsList.length > 0 + ? html`
+ ${map(providerModelsList, this.renderProvider)} +
` + : html``; + } +} + +export default ApplicationWizardAuthenticationMethodChoice; diff --git a/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts b/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts new file mode 100644 index 000000000..aa21bd073 --- /dev/null +++ b/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts @@ -0,0 +1,202 @@ +import { EVENT_REFRESH } from "@goauthentik/app/common/constants"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement, state } from "@lit/reactive-element/decorators.js"; +import { TemplateResult, css, html, nothing } from "lit"; +import { classMap } from "lit/directives/class-map.js"; + +import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; +import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css"; +import PFTitle from "@patternfly/patternfly/components/Title/title.css"; +import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css"; + +import { + ApplicationRequest, + CoreApi, + TransactionApplicationRequest, + TransactionApplicationResponse, +} from "@goauthentik/api"; +import type { ModelRequest } from "@goauthentik/api"; + +import BasePanel from "../BasePanel"; +import providerModelsList from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices"; + +function cleanApplication(app: Partial): ApplicationRequest { + return { + name: "", + slug: "", + ...app, + }; +} + +type ProviderModelType = Exclude; + +type State = { + state: "idle" | "running" | "error" | "success"; + label: string | TemplateResult; + icon: string[]; +}; + +const idleState: State = { + state: "idle", + label: "", + icon: ["fa-cogs", "pf-m-pending"], +}; + +const runningState: State = { + state: "running", + label: msg("Saving Application..."), + icon: ["fa-cogs", "pf-m-info"], +}; +const errorState: State = { + state: "error", + label: msg("Authentik was unable to save this application:"), + icon: ["fa-times-circle", "pf-m-danger"], +}; + +const successState: State = { + state: "success", + label: msg("Your application has been saved"), + icon: ["fa-check-circle", "pf-m-success"], +}; + +@customElement("ak-application-wizard-commit-application") +export class ApplicationWizardCommitApplication extends BasePanel { + static get styles() { + return [ + ...super.styles, + PFBullseye, + PFEmptyState, + PFTitle, + PFProgressStepper, + css` + .pf-c-title { + padding-bottom: var(--pf-global--spacer--md); + } + `, + ]; + } + + @state() + commitState: State = idleState; + + @state() + errors: string[] = []; + + response?: TransactionApplicationResponse; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + willUpdate(_changedProperties: Map) { + if (this.commitState === idleState) { + this.response = undefined; + this.commitState = runningState; + const providerModel = providerModelsList.find( + ({ formName }) => formName === this.wizard.providerModel, + ); + if (!providerModel) { + throw new Error( + `Could not determine provider model from user request: ${JSON.stringify( + this.wizard, + null, + 2, + )}`, + ); + } + + const request: TransactionApplicationRequest = { + providerModel: providerModel.modelName as ProviderModelType, + app: cleanApplication(this.wizard.app), + provider: providerModel.converter(this.wizard.provider), + }; + + this.send(request); + return; + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decodeErrors(body: Record) { + const spaceify = (src: Record) => + 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( + data: TransactionApplicationRequest, + ): Promise { + this.errors = []; + + new CoreApi(DEFAULT_CONFIG) + .coreTransactionalApplicationsUpdate({ + transactionApplicationRequest: data, + }) + .then((response: TransactionApplicationResponse) => { + this.response = response; + this.dispatchCustomEvent(EVENT_REFRESH); + this.dispatchWizardUpdate({ status: "submitted" }); + this.commitState = successState; + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .catch((resolution: any) => { + resolution.response.json().then( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (body: Record) => { + this.errors = this.decodeErrors(body); + }, + ); + this.commitState = errorState; + }); + } + + render(): TemplateResult { + const icon = classMap( + this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {}), + ); + + return html` +
+
+
+
+ +

+ ${this.commitState.label} +

+ ${this.errors.length > 0 + ? html`
    + ${this.errors.map( + (msg) => html`
  • ${msg}
  • `, + )} +
` + : nothing} +
+
+
+
+ `; + } +} + +export default ApplicationWizardCommitApplication; diff --git a/web/src/admin/applications/wizard/ldap/TypeLDAPApplicationWizardPage.ts b/web/src/admin/applications/wizard/ldap/TypeLDAPApplicationWizardPage.ts deleted file mode 100644 index 89c4a5ec8..000000000 --- a/web/src/admin/applications/wizard/ldap/TypeLDAPApplicationWizardPage.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { KeyUnknown } from "@goauthentik/elements/forms/Form"; -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -import { - CoreApi, - FlowDesignationEnum, - FlowsApi, - LDAPProviderRequest, - ProvidersApi, - UserServiceAccountResponse, -} from "@goauthentik/api"; - -@customElement("ak-application-wizard-type-ldap") -export class TypeLDAPApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("LDAP details"); - - nextDataCallback = async (data: KeyUnknown): Promise => { - let name = this.host.state["name"] as string; - // Check if a provider with the name already exists - const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({ - search: name, - }); - if (providers.results.filter((provider) => provider.name == name)) { - name += "-1"; - } - this.host.addActionBefore(msg("Create service account"), async (): Promise => { - const serviceAccount = await new CoreApi(DEFAULT_CONFIG).coreUsersServiceAccountCreate({ - userServiceAccountRequest: { - name: name, - createGroup: true, - }, - }); - this.host.state["serviceAccount"] = serviceAccount; - return true; - }); - this.host.addActionBefore(msg("Create provider"), async (): Promise => { - // Get all flows and default to the implicit authorization - const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ - designation: FlowDesignationEnum.Authorization, - ordering: "slug", - }); - const serviceAccount = this.host.state["serviceAccount"] as UserServiceAccountResponse; - const req: LDAPProviderRequest = { - name: name, - authorizationFlow: flows.results[0].pk, - baseDn: data.baseDN as string, - searchGroup: serviceAccount.groupPk, - }; - const provider = await new ProvidersApi(DEFAULT_CONFIG).providersLdapCreate({ - lDAPProviderRequest: req, - }); - this.host.state["provider"] = provider; - return true; - }); - return true; - }; - - renderForm(): TemplateResult { - const domainParts = window.location.hostname.split("."); - const defaultBaseDN = domainParts.map((part) => `dc=${part}`).join(","); - return html`
- - - -
`; - } -} diff --git a/web/src/admin/applications/wizard/link/TypeLinkApplicationWizardPage.ts b/web/src/admin/applications/wizard/link/TypeLinkApplicationWizardPage.ts deleted file mode 100644 index c91133a45..000000000 --- a/web/src/admin/applications/wizard/link/TypeLinkApplicationWizardPage.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { KeyUnknown } from "@goauthentik/elements/forms/Form"; -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -@customElement("ak-application-wizard-type-link") -export class TypeLinkApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("Application Link"); - - nextDataCallback = async (data: KeyUnknown): Promise => { - this.host.state["link"] = data.link; - return true; - }; - - renderForm(): TemplateResult { - return html` -
- - -

- ${msg("URL which will be opened when a user clicks on the application.")} -

-
-
- `; - } -} diff --git a/web/src/admin/applications/wizard/methods/BaseProviderPanel.ts b/web/src/admin/applications/wizard/methods/BaseProviderPanel.ts new file mode 100644 index 000000000..ce60213e1 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/BaseProviderPanel.ts @@ -0,0 +1,26 @@ +import BasePanel from "../BasePanel"; + +export class ApplicationWizardProviderPageBase extends BasePanel { + handleChange(ev: InputEvent) { + if (!ev.target) { + console.warn(`Received event with no target: ${ev}`); + return; + } + const target = ev.target as HTMLInputElement; + const value = target.type === "checkbox" ? target.checked : target.value; + this.dispatchWizardUpdate({ + update: { + provider: { + [target.name]: value, + }, + }, + status: this.form.checkValidity() ? "valid" : "invalid", + }); + } + + validator() { + return this.form.reportValidity(); + } +} + +export default ApplicationWizardProviderPageBase; diff --git a/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts new file mode 100644 index 000000000..9b7e813bf --- /dev/null +++ b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts @@ -0,0 +1,29 @@ +import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; + +import BasePanel from "../BasePanel"; +import { providerRendererList } from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices"; +import "./ldap/ak-application-wizard-authentication-by-ldap"; +import "./oauth/ak-application-wizard-authentication-by-oauth"; +import "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy"; +import "./proxy/ak-application-wizard-authentication-for-reverse-proxy"; +import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy"; +import "./radius/ak-application-wizard-authentication-by-radius"; +import "./saml/ak-application-wizard-authentication-by-saml-configuration"; +import "./scim/ak-application-wizard-authentication-by-scim"; + +// prettier-ignore + +@customElement("ak-application-wizard-authentication-method") +export class ApplicationWizardApplicationDetails extends BasePanel { + render() { + const handler = providerRendererList.get(this.wizard.providerModel); + if (!handler) { + throw new Error( + "Unrecognized authentication method in ak-application-wizard-authentication-method", + ); + } + return handler(); + } +} + +export default ApplicationWizardApplicationDetails; diff --git a/web/src/admin/applications/wizard/methods/ldap/LDAPOptionsAndHelp.ts b/web/src/admin/applications/wizard/methods/ldap/LDAPOptionsAndHelp.ts new file mode 100644 index 000000000..5b2f1f483 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/ldap/LDAPOptionsAndHelp.ts @@ -0,0 +1,64 @@ +import { msg } from "@lit/localize"; +import { html } from "lit"; + +import { LDAPAPIAccessMode } from "@goauthentik/api"; + +export const bindModeOptions = [ + { + label: msg("Cached binding"), + value: LDAPAPIAccessMode.Cached, + default: true, + description: html`${msg( + "Flow is executed and session is cached in memory. Flow is executed when session expires", + )}`, + }, + { + label: msg("Direct binding"), + value: LDAPAPIAccessMode.Direct, + description: html`${msg( + "Always execute the configured bind flow to authenticate the user", + )}`, + }, +]; + +export const searchModeOptions = [ + { + label: msg("Cached querying"), + value: LDAPAPIAccessMode.Cached, + default: true, + description: html`${msg( + "The outpost holds all users and groups in-memory and will refresh every 5 Minutes", + )}`, + }, + { + label: msg("Direct querying"), + value: LDAPAPIAccessMode.Direct, + description: html`${msg( + "Always returns the latest data, but slower than cached querying", + )}`, + }, +]; + +export const mfaSupportHelp = msg( + "When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.", +); + +export const groupHelp = msg( + "The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber", +); + +export const cryptoCertificateHelp = msg( + "The certificate for the above configured Base DN. As a fallback, the provider uses a self-signed certificate.", +); + +export const tlsServerNameHelp = msg( + "DNS name for which the above configured certificate should be used. The certificate cannot be detected based on the base DN, as the SSL/TLS negotiation happens before such data is exchanged.", +); + +export const uidStartNumberHelp = msg( + "The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber", +); + +export const gidStartNumberHelp = msg( + "The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber", +); diff --git a/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts new file mode 100644 index 000000000..2349af45b --- /dev/null +++ b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts @@ -0,0 +1,146 @@ +import "@goauthentik/admin/common/ak-core-group-search"; +import "@goauthentik/admin/common/ak-crypto-certificate-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-number-input"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import { rootInterface } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; +import { html, nothing } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { FlowsInstancesListDesignationEnum } from "@goauthentik/api"; +import type { LDAPProvider } from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; +import { + bindModeOptions, + cryptoCertificateHelp, + gidStartNumberHelp, + groupHelp, + mfaSupportHelp, + searchModeOptions, + tlsServerNameHelp, + uidStartNumberHelp, +} from "./LDAPOptionsAndHelp"; + +@customElement("ak-application-wizard-authentication-by-ldap") +export class ApplicationWizardApplicationDetails extends BaseProviderPanel { + render() { + const provider = this.wizard.provider as LDAPProvider | undefined; + + return html`
+ + + + +

${msg("Flow used for users to authenticate.")}

+
+ + + +

${groupHelp}

+
+ + + + + + + + + + + + ${msg("Protocol settings")} +
+ + + + + + +

${cryptoCertificateHelp}

+
+ + + + + + +
+
+
`; + } +} + +export default ApplicationWizardApplicationDetails; diff --git a/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts b/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts new file mode 100644 index 000000000..cf6ceb9de --- /dev/null +++ b/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts @@ -0,0 +1,301 @@ +import "@goauthentik/admin/common/ak-crypto-certificate-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import { + clientTypeOptions, + issuerModeOptions, + redirectUriHelp, + subjectModeOptions, +} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-number-input"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/components/ak-textarea-input"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement, state } from "@lit/reactive-element/decorators.js"; +import { html, nothing } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + ClientTypeEnum, + FlowsInstancesListDesignationEnum, + PropertymappingsApi, + SourcesApi, +} from "@goauthentik/api"; +import type { + OAuth2Provider, + PaginatedOAuthSourceList, + PaginatedScopeMappingList, +} from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; + +@customElement("ak-application-wizard-authentication-by-oauth") +export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel { + @state() + showClientSecret = false; + + @state() + propertyMappings?: PaginatedScopeMappingList; + + @state() + oauthSources?: PaginatedOAuthSourceList; + + constructor() { + super(); + new PropertymappingsApi(DEFAULT_CONFIG) + .propertymappingsScopeList({ + ordering: "scope_name", + }) + .then((propertyMappings: PaginatedScopeMappingList) => { + this.propertyMappings = propertyMappings; + }); + + new SourcesApi(DEFAULT_CONFIG) + .sourcesOauthList({ + ordering: "name", + hasJwks: true, + }) + .then((oauthSources: PaginatedOAuthSourceList) => { + this.oauthSources = oauthSources; + }); + } + + render() { + const provider = this.wizard.provider as OAuth2Provider | undefined; + + return html`
+ + + + +

+ ${msg("Flow used when a user access this provider and is not authenticated.")} +

+
+ + + +

+ ${msg("Flow used when authorizing this provider.")} +

+
+ + + ${msg("Protocol settings")} +
+ ) => { + this.showClientSecret = ev.detail !== ClientTypeEnum.Public; + }} + .options=${clientTypeOptions} + > + + + + + + + + + + + + + + +

${msg("Key used to sign the tokens.")}

+
+
+
+ + + ${msg("Advanced protocol settings")} +
+ + ${msg("Configure how long access codes are valid for.")} +

+ `} + > +
+ + + ${msg("Configure how long access tokens are valid for.")} +

+ `} + > +
+ + + ${msg("Configure how long refresh tokens are valid for.")} +

+ `} + > +
+ + + +

+ ${msg( + "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.")} +

+
+ + + + + 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.", + )}> + + +
+
+ + + ${msg("Machine-to-Machine authentication settings")} +
+ + +

+ ${msg( + "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.", + )} +

+

+ ${msg("Hold control/command to select multiple items.")} +

+
+
+
+
`; + } +} + +export default ApplicationWizardAuthenticationByOauth; diff --git a/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts b/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts new file mode 100644 index 000000000..4b32fe2d7 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts @@ -0,0 +1,255 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/components/ak-textarea-input"; +import "@goauthentik/components/ak-toggle-group"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { state } from "@lit/reactive-element/decorators.js"; +import { TemplateResult, html, nothing } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + FlowsInstancesListDesignationEnum, + PaginatedOAuthSourceList, + PaginatedScopeMappingList, + PropertymappingsApi, + ProxyMode, + ProxyProvider, + SourcesApi, +} from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; + +type MaybeTemplateResult = TemplateResult | typeof nothing; + +export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel { + constructor() { + super(); + new PropertymappingsApi(DEFAULT_CONFIG) + .propertymappingsScopeList({ ordering: "scope_name" }) + .then((propertyMappings: PaginatedScopeMappingList) => { + this.propertyMappings = propertyMappings; + }); + + new SourcesApi(DEFAULT_CONFIG) + .sourcesOauthList({ + ordering: "name", + hasJwks: true, + }) + .then((oauthSources: PaginatedOAuthSourceList) => { + this.oauthSources = oauthSources; + }); + } + + propertyMappings?: PaginatedScopeMappingList; + oauthSources?: PaginatedOAuthSourceList; + + @state() + showHttpBasic = true; + + @state() + mode: ProxyMode = ProxyMode.Proxy; + + get instance(): ProxyProvider | undefined { + return this.wizard.provider as ProxyProvider; + } + + renderModeDescription(): MaybeTemplateResult { + return nothing; + } + + renderProxyMode() { + return html`

This space intentionally left blank

`; + } + + renderHttpBasic(): TemplateResult { + return html` + + + + `; + } + + render() { + return html`
+ ${this.renderModeDescription()} + + + + +

+ ${msg("Flow used when a user access this provider and is not authenticated.")} +

+
+ + + +

+ ${msg("Flow used when authorizing this provider.")} +

+
+ + ${this.renderProxyMode()} + + + + + ${msg("Advanced protocol settings")} +
+ + + + + + +

+ ${msg("Additional scope mappings, which are passed to the proxy.")} +

+

+ ${msg("Hold control/command to select multiple items.")} +

+
+ + + ${msg( + "Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.", + )} +

+

+ ${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.", + )} +

`} + > +
+
+
+ + ${msg("Authentication settings")} +
+ + + { + 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.", + )} + > + + ${this.showHttpBasic ? this.renderHttpBasic() : html``} + + + +

+ ${msg( + "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.", + )} +

+

+ ${msg("Hold control/command to select multiple items.")} +

+
+
+
+
`; + } +} + +export default AkTypeProxyApplicationWizardPage; diff --git a/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-forward-domain-proxy.ts b/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-forward-domain-proxy.ts new file mode 100644 index 000000000..e794cd7d4 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-forward-domain-proxy.ts @@ -0,0 +1,55 @@ +import "@goauthentik/components/ak-text-input"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators.js"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage"; + +@customElement("ak-application-wizard-authentication-for-forward-proxy-domain") +export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage { + renderModeDescription() { + return html`

+ ${msg( + "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.", + )} +

+
+ ${msg("An example setup can look like this:")} +
    +
  • ${msg("authentik running on auth.example.com")}
  • +
  • ${msg("app1 running on app1.example.com")}
  • +
+ ${msg( + "In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.", + )} +
`; + } + + renderProxyMode() { + return html` + + + + `; + } +} + +export default AkForwardDomainProxyApplicationWizardPage; diff --git a/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-reverse-proxy.ts b/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-reverse-proxy.ts new file mode 100644 index 000000000..b82d1e538 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-reverse-proxy.ts @@ -0,0 +1,49 @@ +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators.js"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage"; + +@customElement("ak-application-wizard-authentication-for-reverse-proxy") +export class AkReverseProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage { + renderModeDescription() { + return html`

+ ${msg( + "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.", + )} +

`; + } + + renderProxyMode() { + return html` + + + `; + } +} + +export default AkReverseProxyApplicationWizardPage; diff --git a/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-single-forward-proxy.ts b/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-single-forward-proxy.ts new file mode 100644 index 000000000..0840c698f --- /dev/null +++ b/web/src/admin/applications/wizard/methods/proxy/ak-application-wizard-authentication-for-single-forward-proxy.ts @@ -0,0 +1,36 @@ +import "@goauthentik/components/ak-text-input"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators.js"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage"; + +@customElement("ak-application-wizard-authentication-for-single-forward-proxy") +export class AkForwardSingleProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage { + renderModeDescription() { + return html`

+ ${msg( + html`Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you).`, + )} +

`; + } + + renderProxyMode() { + return html``; + } +} + +export default AkForwardSingleProxyApplicationWizardPage; diff --git a/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts new file mode 100644 index 000000000..d107eab0f --- /dev/null +++ b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts @@ -0,0 +1,73 @@ +import "@goauthentik/admin/common/ak-crypto-certificate-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-text-input"; +import { rootInterface } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators.js"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; + +@customElement("ak-application-wizard-authentication-by-radius") +export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel { + render() { + const provider = this.wizard.provider as RadiusProvider | undefined; + + return html`
+ + + + + +

${msg("Flow used for users to authenticate.")}

+
+ + + ${msg("Protocol settings")} +
+ + +
+
+
`; + } +} + +export default ApplicationWizardAuthenticationByRadius; diff --git a/web/src/admin/applications/wizard/methods/saml/SamlProviderOptions.ts b/web/src/admin/applications/wizard/methods/saml/SamlProviderOptions.ts new file mode 100644 index 000000000..a1b6d44a2 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/saml/SamlProviderOptions.ts @@ -0,0 +1,33 @@ +import { msg } from "@lit/localize"; + +import { DigestAlgorithmEnum, SignatureAlgorithmEnum, SpBindingEnum } from "@goauthentik/api"; + +type Option = [string, T, boolean?]; + +function toOptions(options: Option[]) { + return options.map(([label, value, isDefault]: Option) => ({ + label, + value, + default: isDefault ?? false, + })); +} + +export const spBindingOptions = toOptions([ + [msg("Redirect"), SpBindingEnum.Redirect, true], + [msg("Post"), SpBindingEnum.Post], +]); + +export const digestAlgorithmOptions = toOptions([ + ["SHA1", DigestAlgorithmEnum._200009Xmldsigsha1], + ["SHA256", DigestAlgorithmEnum._200104Xmlencsha256, true], + ["SHA384", DigestAlgorithmEnum._200104XmldsigMoresha384], + ["SHA512", DigestAlgorithmEnum._200104Xmlencsha512], +]); + +export const signatureAlgorithmOptions = toOptions([ + ["RSA-SHA1", SignatureAlgorithmEnum._200009XmldsigrsaSha1], + ["RSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMorersaSha256, true], + ["RSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMorersaSha384], + ["RSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMorersaSha512], + ["DSA-SHA1", SignatureAlgorithmEnum._200009XmldsigdsaSha1], +]); diff --git a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts new file mode 100644 index 000000000..f25c995ef --- /dev/null +++ b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts @@ -0,0 +1,250 @@ +import "@goauthentik/admin/common/ak-core-group-search"; +import "@goauthentik/admin/common/ak-crypto-certificate-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-number-input"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/forms/FormGroup"; +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 { ifDefined } from "lit/directives/if-defined.js"; + +import { + FlowsInstancesListDesignationEnum, + PaginatedSAMLPropertyMappingList, + PropertymappingsApi, + SAMLProvider, +} from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; +import { + digestAlgorithmOptions, + signatureAlgorithmOptions, + spBindingOptions, +} from "./SamlProviderOptions"; + +@customElement("ak-application-wizard-authentication-by-saml-configuration") +export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPanel { + propertyMappings?: PaginatedSAMLPropertyMappingList; + + constructor() { + super(); + new PropertymappingsApi(DEFAULT_CONFIG) + .propertymappingsSamlList({ + ordering: "saml_name", + }) + .then((propertyMappings: PaginatedSAMLPropertyMappingList) => { + this.propertyMappings = propertyMappings; + }); + } + + render() { + const provider = this.wizard.provider as SAMLProvider | undefined; + + return html`
+ + + + +

+ ${msg("Flow used when a user access this provider and is not authenticated.")} +

+
+ + + +

+ ${msg("Flow used when authorizing this provider.")} +

+
+ + + ${msg("Protocol settings")} +
+ + + + + + + + +
+
+ + + ${msg("Advanced protocol settings")} +
+ + +

+ ${msg( + "Certificate used to sign outgoing Responses going to the Service Provider.", + )} +

+
+ + + +

+ ${msg( + "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.", + )} +

+
+ + + +

+ ${msg("Hold control/command to select multiple items.")} +

+
+ + + +

+ ${msg( + "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.", + )} +

+
+ + + + + + + + + + + + +
+
+
`; + } +} + +export default ApplicationWizardProviderSamlConfiguration; diff --git a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-import.ts b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-import.ts new file mode 100644 index 000000000..924aead76 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-import.ts @@ -0,0 +1,81 @@ +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`
+ + + + +

+ ${msg("Flow used when authorizing this provider.")} +

+
+ + +
`; + } +} + +export default ApplicationWizardProviderSamlImport; diff --git a/web/src/admin/applications/wizard/methods/saml/saml-property-mappings-search.ts b/web/src/admin/applications/wizard/methods/saml/saml-property-mappings-search.ts new file mode 100644 index 000000000..27b1d53ab --- /dev/null +++ b/web/src/admin/applications/wizard/methods/saml/saml-property-mappings-search.ts @@ -0,0 +1,112 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { AKElement } from "@goauthentik/elements/Base"; +import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; +import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { property, query } from "lit/decorators.js"; + +import { + PropertymappingsApi, + PropertymappingsSamlListRequest, + SAMLPropertyMapping, +} from "@goauthentik/api"; + +async function fetchObjects(query?: string): Promise { + const args: PropertymappingsSamlListRequest = { + ordering: "saml_name", + }; + if (query !== undefined) { + args.search = query; + } + const items = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlList(args); + return items.results; +} + +function renderElement(item: SAMLPropertyMapping): string { + return item.name; +} + +function renderValue(item: SAMLPropertyMapping | undefined): string | undefined { + return item?.pk; +} + +/** + * SAML Property Mapping Search + * + * @element ak-saml-property-mapping-search + * + * A wrapper around SearchSelect for the SAML Property Search. It's a unique search, but for the + * purpose of the form all you need to know is that it is being searched and selected. Let's put the + * how somewhere else. + * + */ + +@customElement("ak-saml-property-mapping-search") +export class SAMLPropertyMappingSearch extends CustomListenerElement(AKElement) { + /** + * The current property mapping known to the caller. + * + * @attr + */ + @property({ type: String, reflect: true, attribute: "propertymapping" }) + propertyMapping?: string; + + @query("ak-search-select") + search!: SearchSelect; + + @property({ type: String }) + name: string | null | undefined; + + selectedPropertyMapping?: SAMLPropertyMapping; + + constructor() { + super(); + this.selected = this.selected.bind(this); + this.handleSearchUpdate = this.handleSearchUpdate.bind(this); + this.addCustomListener("ak-change", this.handleSearchUpdate); + } + + get value() { + return this.selectedPropertyMapping ? renderValue(this.selectedPropertyMapping) : undefined; + } + + connectedCallback() { + super.connectedCallback(); + const horizontalContainer = this.closest("ak-form-element-horizontal[name]"); + if (!horizontalContainer) { + throw new Error("This search can only be used in a named ak-form-element-horizontal"); + } + const name = horizontalContainer.getAttribute("name"); + const myName = this.getAttribute("name"); + if (name !== null && name !== myName) { + this.setAttribute("name", name); + } + } + + handleSearchUpdate(ev: CustomEvent) { + ev.stopPropagation(); + this.selectedPropertyMapping = ev.detail.value; + this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true })); + } + + selected(item: SAMLPropertyMapping): boolean { + return this.propertyMapping === item.pk; + } + + render() { + return html` + + + `; + } +} + +export default SAMLPropertyMappingSearch; diff --git a/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts b/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts new file mode 100644 index 000000000..493c740d1 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts @@ -0,0 +1,189 @@ +import "@goauthentik/admin/common/ak-crypto-certificate-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement, state } from "@lit/reactive-element/decorators.js"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + CoreApi, + CoreGroupsListRequest, + type Group, + PaginatedSCIMMappingList, + PropertymappingsApi, + type SCIMProvider, +} from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; + +@customElement("ak-application-wizard-authentication-by-scim") +export class ApplicationWizardAuthenticationBySCIM extends BaseProviderPanel { + @state() + propertyMappings?: PaginatedSCIMMappingList; + + constructor() { + super(); + new PropertymappingsApi(DEFAULT_CONFIG) + .propertymappingsScopeList({ + ordering: "scope_name", + }) + .then((propertyMappings: PaginatedSCIMMappingList) => { + this.propertyMappings = propertyMappings; + }); + } + + render() { + const provider = this.wizard.provider as SCIMProvider | undefined; + + return html`
+ + + ${msg("Protocol settings")} +
+ + + + +
+
+ + ${msg("User filtering")} +
+ + + => { + 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} + > + +

+ ${msg("Only sync users within the selected group.")} +

+
+
+
+ + ${msg("Attribute mapping")} +
+ + +

+ ${msg("Property mappings used to user mapping.")} +

+

+ ${msg("Hold control/command to select multiple items.")} +

+
+ + +

+ ${msg("Property mappings used to group creation.")} +

+

+ ${msg("Hold control/command to select multiple items.")} +

+
+
+
+
`; + } +} + +export default ApplicationWizardAuthenticationBySCIM; diff --git a/web/src/admin/applications/wizard/oauth/TypeOAuthAPIApplicationWizardPage.ts b/web/src/admin/applications/wizard/oauth/TypeOAuthAPIApplicationWizardPage.ts deleted file mode 100644 index a2e08c2aa..000000000 --- a/web/src/admin/applications/wizard/oauth/TypeOAuthAPIApplicationWizardPage.ts +++ /dev/null @@ -1,35 +0,0 @@ -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -@customElement("ak-application-wizard-type-oauth-api") -export class TypeOAuthAPIApplicationWizardPage extends WizardPage { - static get styles(): CSSResult[] { - return [PFBase, PFButton, PFForm, PFRadio]; - } - - sidebarLabel = () => msg("Method details"); - - render(): TemplateResult { - return html`
-

- ${msg( - "This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.", - )} -

-

- ${msg( - "By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.", - )} -

-
`; - } -} diff --git a/web/src/admin/applications/wizard/oauth/TypeOAuthApplicationWizardPage.ts b/web/src/admin/applications/wizard/oauth/TypeOAuthApplicationWizardPage.ts deleted file mode 100644 index bb19272e9..000000000 --- a/web/src/admin/applications/wizard/oauth/TypeOAuthApplicationWizardPage.ts +++ /dev/null @@ -1,84 +0,0 @@ -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -import { TypeCreate } from "@goauthentik/api"; - -@customElement("ak-application-wizard-type-oauth") -export class TypeOAuthApplicationWizardPage extends WizardPage { - applicationTypes: TypeCreate[] = [ - { - component: "ak-application-wizard-type-oauth-code", - name: msg("Web application"), - description: msg( - "Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)", - ), - modelName: "", - }, - { - component: "ak-application-wizard-type-oauth-implicit", - name: msg("Single-page applications"), - description: msg( - "Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)", - ), - modelName: "", - }, - { - component: "ak-application-wizard-type-oauth-implicit", - name: msg("Native application"), - description: msg( - "Applications which redirect users to a non-web callback (for example, Android, iOS)", - ), - modelName: "", - }, - { - component: "ak-application-wizard-type-oauth-api", - name: msg("API"), - description: msg( - "Authentication without user interaction, or machine-to-machine authentication.", - ), - modelName: "", - }, - ]; - - static get styles(): CSSResult[] { - return [PFBase, PFButton, PFForm, PFRadio]; - } - - sidebarLabel = () => msg("Application type"); - - render(): TemplateResult { - return html`
- ${this.applicationTypes.map((type) => { - return html`
- { - this.host.steps = [ - "ak-application-wizard-initial", - "ak-application-wizard-type", - "ak-application-wizard-type-oauth", - type.component, - ]; - this.host.state["oauth-type"] = type.component; - this.host.isValid = true; - }} - /> - - ${type.description} -
`; - })} -
`; - } -} diff --git a/web/src/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage.ts b/web/src/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage.ts deleted file mode 100644 index 458def24b..000000000 --- a/web/src/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage.ts +++ /dev/null @@ -1,57 +0,0 @@ -import "@goauthentik/admin/common/ak-flow-search/ak-flow-search-no-default"; -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { KeyUnknown } from "@goauthentik/elements/forms/Form"; -import "@goauthentik/elements/forms/HorizontalFormElement"; -import "@goauthentik/elements/forms/SearchSelect"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; -import "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -import { - ClientTypeEnum, - FlowsInstancesListDesignationEnum, - OAuth2ProviderRequest, - ProvidersApi, -} from "@goauthentik/api"; - -@customElement("ak-application-wizard-type-oauth-code") -export class TypeOAuthCodeApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("Method details"); - - nextDataCallback = async (data: KeyUnknown): Promise => { - this.host.addActionBefore(msg("Create provider"), async (): Promise => { - const req: OAuth2ProviderRequest = { - name: this.host.state["name"] as string, - clientType: ClientTypeEnum.Confidential, - authorizationFlow: data.authorizationFlow as string, - }; - const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({ - oAuth2ProviderRequest: req, - }); - this.host.state["provider"] = provider; - return true; - }); - return true; - }; - - renderForm(): TemplateResult { - return html`
- - -

- ${msg("Flow used when users access this application.")} -

-
-
`; - } -} diff --git a/web/src/admin/applications/wizard/oauth/TypeOAuthImplicitApplicationWizardPage.ts b/web/src/admin/applications/wizard/oauth/TypeOAuthImplicitApplicationWizardPage.ts deleted file mode 100644 index c1fa0f3cf..000000000 --- a/web/src/admin/applications/wizard/oauth/TypeOAuthImplicitApplicationWizardPage.ts +++ /dev/null @@ -1,15 +0,0 @@ -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -@customElement("ak-application-wizard-type-oauth-implicit") -export class TypeOAuthImplicitApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("Method details"); - - render(): TemplateResult { - return html`
some stuff idk
`; - } -} diff --git a/web/src/admin/applications/wizard/proxy/TypeProxyApplicationWizardPage.ts b/web/src/admin/applications/wizard/proxy/TypeProxyApplicationWizardPage.ts deleted file mode 100644 index 43db7c56c..000000000 --- a/web/src/admin/applications/wizard/proxy/TypeProxyApplicationWizardPage.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { KeyUnknown } from "@goauthentik/elements/forms/Form"; -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -import { - FlowDesignationEnum, - FlowsApi, - ProvidersApi, - ProxyProviderRequest, -} from "@goauthentik/api"; - -@customElement("ak-application-wizard-type-proxy") -export class TypeProxyApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("Proxy details"); - - nextDataCallback = async (data: KeyUnknown): Promise => { - let name = this.host.state["name"] as string; - // Check if a provider with the name already exists - const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({ - search: name, - }); - if (providers.results.filter((provider) => provider.name == name)) { - name += "-1"; - } - this.host.addActionBefore(msg("Create provider"), async (): Promise => { - // Get all flows and default to the implicit authorization - const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ - designation: FlowDesignationEnum.Authorization, - ordering: "slug", - }); - const req: ProxyProviderRequest = { - name: name, - authorizationFlow: flows.results[0].pk, - externalHost: data.externalHost as string, - }; - const provider = await new ProvidersApi(DEFAULT_CONFIG).providersProxyCreate({ - proxyProviderRequest: req, - }); - this.host.state["provider"] = provider; - return true; - }); - return true; - }; - - renderForm(): TemplateResult { - return html`
- - -

- ${msg("External domain you will be accessing the domain from.")} -

-
-
`; - } -} diff --git a/web/src/admin/applications/wizard/saml/TypeSAMLApplicationWizardPage.ts b/web/src/admin/applications/wizard/saml/TypeSAMLApplicationWizardPage.ts deleted file mode 100644 index 2ef4d6972..000000000 --- a/web/src/admin/applications/wizard/saml/TypeSAMLApplicationWizardPage.ts +++ /dev/null @@ -1,66 +0,0 @@ -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -import { TypeCreate } from "@goauthentik/api"; - -@customElement("ak-application-wizard-type-saml") -export class TypeOAuthApplicationWizardPage extends WizardPage { - applicationTypes: TypeCreate[] = [ - { - component: "ak-application-wizard-type-saml-import", - name: msg("Import SAML Metadata"), - description: msg( - "Import the metadata document of the applicaation you want to configure.", - ), - modelName: "", - }, - { - component: "ak-application-wizard-type-saml-config", - name: msg("Manual configuration"), - description: msg("Manually configure SAML"), - modelName: "", - }, - ]; - - static get styles(): CSSResult[] { - return [PFBase, PFButton, PFForm, PFRadio]; - } - - sidebarLabel = () => msg("Application type"); - - render(): TemplateResult { - return html`
- ${this.applicationTypes.map((type) => { - return html`
- { - this.host.steps = [ - "ak-application-wizard-initial", - "ak-application-wizard-type", - "ak-application-wizard-type-saml", - type.component, - ]; - this.host.state["saml-type"] = type.component; - this.host.isValid = true; - }} - /> - - ${type.description} -
`; - })} -
`; - } -} diff --git a/web/src/admin/applications/wizard/saml/TypeSAMLConfigApplicationWizardPage.ts b/web/src/admin/applications/wizard/saml/TypeSAMLConfigApplicationWizardPage.ts deleted file mode 100644 index ad269ac63..000000000 --- a/web/src/admin/applications/wizard/saml/TypeSAMLConfigApplicationWizardPage.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { KeyUnknown } from "@goauthentik/elements/forms/Form"; -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -import { FlowDesignationEnum, FlowsApi, ProvidersApi, SAMLProviderRequest } from "@goauthentik/api"; - -@customElement("ak-application-wizard-type-saml-config") -export class TypeSAMLApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("SAML details"); - - nextDataCallback = async (data: KeyUnknown): Promise => { - let name = this.host.state["name"] as string; - // Check if a provider with the name already exists - const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({ - search: name, - }); - if (providers.results.filter((provider) => provider.name == name)) { - name += "-1"; - } - this.host.addActionBefore(msg("Create provider"), async (): Promise => { - // Get all flows and default to the implicit authorization - const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ - designation: FlowDesignationEnum.Authorization, - ordering: "slug", - }); - const req: SAMLProviderRequest = { - name: name, - authorizationFlow: flows.results[0].pk, - acsUrl: data.acsUrl as string, - }; - const provider = await new ProvidersApi(DEFAULT_CONFIG).providersSamlCreate({ - sAMLProviderRequest: req, - }); - this.host.state["provider"] = provider; - return true; - }); - return true; - }; - - renderForm(): TemplateResult { - return html`
- - -

- ${msg( - "URL that authentik will redirect back to after successful authentication.", - )} -

-
-
`; - } -} diff --git a/web/src/admin/applications/wizard/saml/TypeSAMLImportApplicationWizardPage.ts b/web/src/admin/applications/wizard/saml/TypeSAMLImportApplicationWizardPage.ts deleted file mode 100644 index c3ddfda79..000000000 --- a/web/src/admin/applications/wizard/saml/TypeSAMLImportApplicationWizardPage.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { KeyUnknown } from "@goauthentik/elements/forms/Form"; -import "@goauthentik/elements/forms/HorizontalFormElement"; -import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage"; - -import { msg } from "@lit/localize"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { TemplateResult, html } from "lit"; - -import { - FlowDesignationEnum, - FlowsApi, - ProvidersApi, - ProvidersSamlImportMetadataCreateRequest, -} from "@goauthentik/api"; - -@customElement("ak-application-wizard-type-saml-import") -export class TypeSAMLImportApplicationWizardPage extends WizardFormPage { - sidebarLabel = () => msg("Import SAML metadata"); - - nextDataCallback = async (data: KeyUnknown): Promise => { - let name = this.host.state["name"] as string; - // Check if a provider with the name already exists - const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({ - search: name, - }); - if (providers.results.filter((provider) => provider.name == name)) { - name += "-1"; - } - this.host.addActionBefore(msg("Create provider"), async (): Promise => { - // Get all flows and default to the implicit authorization - const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ - designation: FlowDesignationEnum.Authorization, - ordering: "slug", - }); - const req: ProvidersSamlImportMetadataCreateRequest = { - name: name, - authorizationFlow: flows.results[0].slug, - file: data["metadata"] as Blob, - }; - const provider = await new ProvidersApi( - DEFAULT_CONFIG, - ).providersSamlImportMetadataCreate(req); - this.host.state["provider"] = provider; - return true; - }); - return true; - }; - - renderForm(): TemplateResult { - return html`
- - - -
`; - } -} diff --git a/web/src/admin/applications/wizard/steps.ts b/web/src/admin/applications/wizard/steps.ts new file mode 100644 index 000000000..451367bf8 --- /dev/null +++ b/web/src/admin/applications/wizard/steps.ts @@ -0,0 +1,82 @@ +import { + BackStep, + CancelWizard, + CloseWizard, + DisabledNextStep, + NextStep, + SubmitStep, +} from "@goauthentik/components/ak-wizard-main/commonWizardButtons"; + +import { html } from "lit"; + +import "./application/ak-application-wizard-application-details"; +import "./auth-method-choice/ak-application-wizard-authentication-method-choice"; +import "./commit/ak-application-wizard-commit-application"; +import "./methods/ak-application-wizard-authentication-method"; +import { ApplicationStep as ApplicationStepType } from "./types"; + +class ApplicationStep implements ApplicationStepType { + id = "application"; + label = "Application Details"; + disabled = false; + valid = false; + get buttons() { + return [this.valid ? NextStep : DisabledNextStep, CancelWizard]; + } + render() { + return html``; + } +} + +class ProviderMethodStep implements ApplicationStepType { + id = "provider-method"; + label = "Provider Type"; + disabled = false; + valid = false; + + get buttons() { + return [BackStep, this.valid ? NextStep : DisabledNextStep, CancelWizard]; + } + + render() { + // prettier-ignore + return html` `; + } +} + +class ProviderStepDetails implements ApplicationStepType { + id = "provider-details"; + label = "Provider Configuration"; + disabled = true; + valid = false; + get buttons() { + return [BackStep, this.valid ? SubmitStep : DisabledNextStep, CancelWizard]; + } + + render() { + return html``; + } +} + +class SubmitApplicationStep implements ApplicationStepType { + id = "submit"; + label = "Submit Application"; + disabled = true; + valid = false; + + get buttons() { + return this.valid ? [CloseWizard] : [BackStep, CancelWizard]; + } + + render() { + return html``; + } +} + +export const newSteps = (): ApplicationStep[] => [ + new ApplicationStep(), + new ProviderMethodStep(), + new ProviderStepDetails(), + new SubmitApplicationStep(), +]; diff --git a/web/src/admin/applications/wizard/stories/ak-application-context-display-for-test.ts b/web/src/admin/applications/wizard/stories/ak-application-context-display-for-test.ts new file mode 100644 index 000000000..fa418199e --- /dev/null +++ b/web/src/admin/applications/wizard/stories/ak-application-context-display-for-test.ts @@ -0,0 +1,18 @@ +import { consume } from "@lit-labs/context"; +import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; +import { state } from "@lit/reactive-element/decorators/state.js"; +import { LitElement, html } from "lit"; + +import applicationWizardContext from "../ContextIdentity"; +import type { ApplicationWizardState } from "../types"; + +@customElement("ak-application-context-display-for-test") +export class ApplicationContextDisplayForTest extends LitElement { + @consume({ context: applicationWizardContext, subscribe: true }) + @state() + private wizard!: ApplicationWizardState; + + render() { + return html`
${JSON.stringify(this.wizard, null, 2)}
`; + } +} diff --git a/web/src/admin/applications/wizard/stories/ak-application-wizard-main.stories.ts b/web/src/admin/applications/wizard/stories/ak-application-wizard-main.stories.ts new file mode 100644 index 000000000..d0e7d8aec --- /dev/null +++ b/web/src/admin/applications/wizard/stories/ak-application-wizard-main.stories.ts @@ -0,0 +1,54 @@ +import { Meta } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +import { ApplicationWizard } from "../ak-application-wizard"; +import "../ak-application-wizard"; +import { mockData } from "./mockData"; + +const metadata: Meta = { + title: "Elements / Application Wizard Implementation / Main", + component: "ak-application-wizard", + parameters: { + docs: { + description: { + component: "The first page of the application wizard", + }, + }, + mockData, + }, +}; + +const LIGHT = "pf-t-light"; +function injectTheme() { + setTimeout(() => { + if (!document.body.classList.contains(LIGHT)) { + document.body.classList.add(LIGHT); + } + }); +} + +export default metadata; + +const container = (testItem: TemplateResult) => { + injectTheme(); + return html`
+ + ${testItem} +
`; +}; + +export const MainPage = () => { + return container(html` + +
+ + `); +}; diff --git a/web/src/admin/applications/wizard/stories/mockData.ts b/web/src/admin/applications/wizard/stories/mockData.ts new file mode 100644 index 000000000..3bd5be087 --- /dev/null +++ b/web/src/admin/applications/wizard/stories/mockData.ts @@ -0,0 +1,62 @@ +import { + dummyAuthenticationFlowsSearch, + dummyAuthorizationFlowsSearch, + dummyCoreGroupsSearch, + dummyCryptoCertsSearch, + dummyHasJwks, + dummyPropertyMappings, + dummyProviderTypesList, + dummySAMLProviderMappings, +} from "./samples"; + +export const mockData = [ + { + url: "/api/v3/providers/all/types/", + method: "GET", + status: 200, + response: dummyProviderTypesList, + }, + { + url: "/api/v3/core/groups/?ordering=name", + method: "GET", + status: 200, + response: dummyCoreGroupsSearch, + }, + + { + url: "/api/v3/crypto/certificatekeypairs/?has_key=true&include_details=false&ordering=name", + method: "GET", + status: 200, + response: dummyCryptoCertsSearch, + }, + { + url: "/api/v3/flows/instances/?designation=authentication&ordering=slug", + method: "GET", + status: 200, + response: dummyAuthenticationFlowsSearch, + }, + { + url: "/api/v3/flows/instances/?designation=authorization&ordering=slug", + method: "GET", + status: 200, + response: dummyAuthorizationFlowsSearch, + }, + { + url: "/api/v3/propertymappings/scope/?ordering=scope_name", + method: "GET", + status: 200, + response: dummyPropertyMappings, + }, + { + url: "/api/v3/sources/oauth/?has_jwks=true&ordering=name", + method: "GET", + status: 200, + response: dummyHasJwks, + }, + { + url: "/api/v3/propertymappings/saml/?ordering=saml_name", + method: "GET", + status: 200, + response: dummySAMLProviderMappings, + }, +]; diff --git a/web/src/admin/applications/wizard/stories/samples.ts b/web/src/admin/applications/wizard/stories/samples.ts new file mode 100644 index 000000000..27b5867aa --- /dev/null +++ b/web/src/admin/applications/wizard/stories/samples.ts @@ -0,0 +1,375 @@ +export const dummyCryptoCertsSearch = { + pagination: { + next: 0, + previous: 0, + count: 1, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 1, + }, + results: [ + { + pk: "63efd1b8-6c39-4f65-8157-9a406cb37447", + name: "authentik Self-signed Certificate", + fingerprint_sha256: null, + fingerprint_sha1: null, + cert_expiry: null, + cert_subject: null, + private_key_available: true, + private_key_type: null, + certificate_download_url: + "/api/v3/crypto/certificatekeypairs/63efd1b8-6c39-4f65-8157-9a406cb37447/view_certificate/?download", + private_key_download_url: + "/api/v3/crypto/certificatekeypairs/63efd1b8-6c39-4f65-8157-9a406cb37447/view_private_key/?download", + managed: null, + }, + ], +}; + +export const dummyAuthenticationFlowsSearch = { + pagination: { + next: 0, + previous: 0, + count: 2, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 2, + }, + results: [ + { + pk: "2594b1a0-f234-4965-8b93-a8631a55bd5c", + policybindingmodel_ptr_id: "0bc529a6-dcd0-4ba8-8fef-5702348832f9", + name: "Welcome to authentik!", + slug: "default-authentication-flow", + title: "Welcome to authentik!", + designation: "authentication", + background: "/static/dist/assets/images/flow_background.jpg", + stages: [ + "bad9fbce-fb86-4ba4-8124-e7a1d8c147f3", + "1da1f272-a76e-4112-be95-f02421fca1d4", + "945cd956-6670-4dfa-ab3a-2a72dd3051a7", + "0fc1fc5c-b928-4d99-a892-9ae48de089f5", + ], + policies: [], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: "/api/v3/flows/instances/default-authentication-flow/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "none", + }, + { + pk: "3526dbd1-b50e-4553-bada-fbe7b3c2f660", + policybindingmodel_ptr_id: "cde67954-b78a-4fe9-830e-c2aba07a724a", + name: "Welcome to authentik!", + slug: "default-source-authentication", + title: "Welcome to authentik!", + designation: "authentication", + background: "/static/dist/assets/images/flow_background.jpg", + stages: ["3713b252-cee3-4acb-a02f-083f26459fff"], + policies: ["f42a4c7f-6586-4b14-9325-a832127ba295"], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: "/api/v3/flows/instances/default-source-authentication/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "require_unauthenticated", + }, + ], +}; + +export const dummyAuthorizationFlowsSearch = { + pagination: { + next: 0, + previous: 0, + count: 2, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 2, + }, + results: [ + { + pk: "9e01f011-8b3f-43d6-bedf-c29be5f3a428", + policybindingmodel_ptr_id: "14179ef8-2726-4027-9e2f-dc99185199bf", + name: "Authorize Application", + slug: "default-provider-authorization-explicit-consent", + title: "Redirecting to %(app)s", + designation: "authorization", + background: "/static/dist/assets/images/flow_background.jpg", + stages: ["ed5f015f-82b9-450f-addf-1e9d21d8dda3"], + policies: [], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: + "/api/v3/flows/instances/default-provider-authorization-explicit-consent/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "require_authenticated", + }, + { + pk: "06f11ee3-cbe3-456d-81df-fae4c0a62951", + policybindingmodel_ptr_id: "686e6539-8b9f-473e-9f54-e05cc207dd2a", + name: "Authorize Application", + slug: "default-provider-authorization-implicit-consent", + title: "Redirecting to %(app)s", + designation: "authorization", + background: "/static/dist/assets/images/flow_background.jpg", + stages: [], + policies: [], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: + "/api/v3/flows/instances/default-provider-authorization-implicit-consent/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "require_authenticated", + }, + ], +}; + +export const dummyCoreGroupsSearch = { + pagination: { + next: 0, + previous: 0, + count: 1, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 1, + }, + results: [ + { + pk: "67543d37-0ee2-4a4c-b020-9e735a8b5178", + num_pk: 13734, + name: "authentik Admins", + is_superuser: true, + parent: null, + users: [1], + attributes: {}, + users_obj: [ + { + pk: 1, + username: "akadmin", + name: "authentik Default Admin", + is_active: true, + last_login: "2023-07-03T16:08:11.196942Z", + email: "ken@goauthentik.io", + attributes: { + settings: { + locale: "en", + }, + }, + uid: "6dedc98b3fdd0f9afdc705e9d577d61127d89f1d91ea2f90f0b9a353615fb8f2", + }, + ], + }, + ], +}; + +export const dummyPropertyMappings = { + pagination: { + next: 0, + previous: 0, + count: 4, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 4, + }, + results: [ + { + pk: "30d87af7-9d9d-4292-873e-a52145ba4bcb", + managed: "goauthentik.io/providers/proxy/scope-proxy", + name: "authentik default OAuth Mapping: Proxy outpost", + expression: + '# This mapping is used by the authentik proxy. It passes extra user attributes,\n# which are used for example for the HTTP-Basic Authentication mapping.\nreturn {\n "ak_proxy": {\n "user_attributes": request.user.group_attributes(request),\n "is_superuser": request.user.is_superuser,\n }\n}', + component: "ak-property-mapping-scope-form", + verbose_name: "Scope Mapping", + verbose_name_plural: "Scope Mappings", + meta_model_name: "authentik_providers_oauth2.scopemapping", + scope_name: "ak_proxy", + description: "authentik Proxy - User information", + }, + { + pk: "3e3751ed-a24c-4f47-a051-e2e05b5cd306", + managed: "goauthentik.io/providers/oauth2/scope-email", + name: "authentik default OAuth Mapping: OpenID 'email'", + expression: 'return {\n "email": request.user.email,\n "email_verified": True\n}', + component: "ak-property-mapping-scope-form", + verbose_name: "Scope Mapping", + verbose_name_plural: "Scope Mappings", + meta_model_name: "authentik_providers_oauth2.scopemapping", + scope_name: "email", + description: "Email address", + }, + { + pk: "81c5e330-d8a0-45cd-9cad-e6a49a9c428f", + managed: "goauthentik.io/providers/oauth2/scope-openid", + name: "authentik default OAuth Mapping: OpenID 'openid'", + expression: + "# This scope is required by the OpenID-spec, and must as such exist in authentik.\n# The scope by itself does not grant any information\nreturn {}", + component: "ak-property-mapping-scope-form", + verbose_name: "Scope Mapping", + verbose_name_plural: "Scope Mappings", + meta_model_name: "authentik_providers_oauth2.scopemapping", + scope_name: "openid", + description: "", + }, + { + pk: "7ad9cd6f-bcc8-425d-b7c2-c7c4592a1b36", + managed: "goauthentik.io/providers/oauth2/scope-profile", + name: "authentik default OAuth Mapping: OpenID 'profile'", + expression: + 'return {\n # Because authentik only saves the user\'s full name, and has no concept of first and last names,\n # the full name is used as given name.\n # You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`\n "name": request.user.name,\n "given_name": request.user.name,\n "preferred_username": request.user.username,\n "nickname": request.user.username,\n # groups is not part of the official userinfo schema, but is a quasi-standard\n "groups": [group.name for group in request.user.ak_groups.all()],\n}', + component: "ak-property-mapping-scope-form", + verbose_name: "Scope Mapping", + verbose_name_plural: "Scope Mappings", + meta_model_name: "authentik_providers_oauth2.scopemapping", + scope_name: "profile", + description: "General Profile Information", + }, + ], +}; + +export const dummyHasJwks = { + pagination: { + next: 0, + previous: 0, + count: 0, + current: 1, + total_pages: 1, + start_index: 0, + end_index: 0, + }, + results: [], +}; + +export const dummySAMLProviderMappings = { + pagination: { + next: 0, + previous: 0, + count: 7, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 7, + }, + results: [ + { + pk: "9f1f23b7-1956-4daa-b08b-338cab9b3953", + managed: "goauthentik.io/providers/saml/uid", + name: "authentik default SAML Mapping: User ID", + expression: "return request.user.pk", + component: "ak-property-mapping-saml-form", + verbose_name: "SAML Property Mapping", + verbose_name_plural: "SAML Property Mappings", + meta_model_name: "authentik_providers_saml.samlpropertymapping", + saml_name: "http://schemas.goauthentik.io/2021/02/saml/uid", + friendly_name: null, + }, + { + pk: "801b6328-bb0b-4ec6-b52c-f3dc7bb6ec7f", + managed: "goauthentik.io/providers/saml/username", + name: "authentik default SAML Mapping: Username", + expression: "return request.user.username", + component: "ak-property-mapping-saml-form", + verbose_name: "SAML Property Mapping", + verbose_name_plural: "SAML Property Mappings", + meta_model_name: "authentik_providers_saml.samlpropertymapping", + saml_name: "http://schemas.goauthentik.io/2021/02/saml/username", + friendly_name: null, + }, + { + pk: "27c4d370-658d-4acf-9f61-cfa6dd020b11", + managed: "goauthentik.io/providers/saml/ms-windowsaccountname", + name: "authentik default SAML Mapping: WindowsAccountname (Username)", + expression: "return request.user.username", + component: "ak-property-mapping-saml-form", + verbose_name: "SAML Property Mapping", + verbose_name_plural: "SAML Property Mappings", + meta_model_name: "authentik_providers_saml.samlpropertymapping", + saml_name: "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", + friendly_name: null, + }, + { + pk: "757b185b-1c21-42b4-a2ee-04d6f7b655b3", + managed: "goauthentik.io/providers/saml/groups", + name: "authentik default SAML Mapping: Groups", + expression: "for group in request.user.ak_groups.all():\n yield group.name", + component: "ak-property-mapping-saml-form", + verbose_name: "SAML Property Mapping", + verbose_name_plural: "SAML Property Mappings", + meta_model_name: "authentik_providers_saml.samlpropertymapping", + saml_name: "http://schemas.xmlsoap.org/claims/Group", + friendly_name: null, + }, + { + pk: "de67cee7-7c56-4c1d-9466-9ad0e0105092", + managed: "goauthentik.io/providers/saml/email", + name: "authentik default SAML Mapping: Email", + expression: "return request.user.email", + component: "ak-property-mapping-saml-form", + verbose_name: "SAML Property Mapping", + verbose_name_plural: "SAML Property Mappings", + meta_model_name: "authentik_providers_saml.samlpropertymapping", + saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + friendly_name: null, + }, + { + pk: "42a936a5-11a9-4442-8748-ec27a8ab9546", + managed: "goauthentik.io/providers/saml/name", + name: "authentik default SAML Mapping: Name", + expression: "return request.user.name", + component: "ak-property-mapping-saml-form", + verbose_name: "SAML Property Mapping", + verbose_name_plural: "SAML Property Mappings", + meta_model_name: "authentik_providers_saml.samlpropertymapping", + saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", + friendly_name: null, + }, + { + pk: "06bee8f0-e5b4-4ce8-959a-308ba0769917", + managed: "goauthentik.io/providers/saml/upn", + name: "authentik default SAML Mapping: UPN", + expression: "return request.user.attributes.get('upn', request.user.email)", + component: "ak-property-mapping-saml-form", + verbose_name: "SAML Property Mapping", + verbose_name_plural: "SAML Property Mappings", + meta_model_name: "authentik_providers_saml.samlpropertymapping", + saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", + friendly_name: null, + }, + ], +}; + +// prettier-ignore +export const dummyProviderTypesList = [ + ["LDAP Provider", "ldapprovider", + "Allow applications to authenticate against authentik's users using LDAP.", + ], + ["OAuth2/OpenID Provider", "oauth2provider", + "OAuth2 Provider for generic OAuth and OpenID Connect Applications.", + ], + ["Proxy Provider", "proxyprovider", + "Protect applications that don't support any of the other\n Protocols by using a Reverse-Proxy.", + ], + ["Radius Provider", "radiusprovider", + "Allow applications to authenticate against authentik's users using Radius.", + ], + ["SAML Provider", "samlprovider", + "SAML 2.0 Endpoint for applications which support SAML.", + ], + ["SCIM Provider", "scimprovider", + "SCIM 2.0 provider to create users and groups in external applications", + ], + ["SAML Provider from Metadata", "", + "Create a SAML Provider by importing its Metadata.", + ], +].map(([name, model_name, description]) => ({ name, description, model_name })); diff --git a/web/src/admin/applications/wizard/types.ts b/web/src/admin/applications/wizard/types.ts new file mode 100644 index 000000000..0ebe7aa8a --- /dev/null +++ b/web/src/admin/applications/wizard/types.ts @@ -0,0 +1,39 @@ +import { type WizardStep } from "@goauthentik/components/ak-wizard-main/types"; + +import { + type ApplicationRequest, + type LDAPProviderRequest, + type OAuth2ProviderRequest, + type ProvidersSamlImportMetadataCreateRequest, + type ProxyProviderRequest, + type RadiusProviderRequest, + type SAMLProviderRequest, + type SCIMProviderRequest, +} from "@goauthentik/api"; + +export type OneOfProvider = + | Partial + | Partial + | Partial + | Partial + | Partial + | Partial + | Partial; + +export interface ApplicationWizardState { + providerModel: string; + app: Partial; + provider: OneOfProvider; +} + +type StatusType = "invalid" | "valid" | "submitted" | "failed"; + +export type ApplicationWizardStateUpdate = { + update?: Partial; + status?: StatusType; +}; + +export type ApplicationStep = WizardStep & { + id: string; + valid: boolean; +}; diff --git a/web/src/admin/blueprints/BlueprintListPage.ts b/web/src/admin/blueprints/BlueprintListPage.ts index e2e06351d..f038cc957 100644 --- a/web/src/admin/blueprints/BlueprintListPage.ts +++ b/web/src/admin/blueprints/BlueprintListPage.ts @@ -7,6 +7,7 @@ import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -18,7 +19,12 @@ import { customElement, property } from "lit/decorators.js"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; -import { BlueprintInstance, BlueprintInstanceStatusEnum, ManagedApi } from "@goauthentik/api"; +import { + BlueprintInstance, + BlueprintInstanceStatusEnum, + ManagedApi, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; export function BlueprintStatus(blueprint?: BlueprintInstance): string { if (!blueprint) return ""; @@ -151,6 +157,11 @@ export class BlueprintListPage extends TablePage { + + { diff --git a/web/src/admin/crypto/CertificateKeyPairListPage.ts b/web/src/admin/crypto/CertificateKeyPairListPage.ts index c6959ea40..990f3446e 100644 --- a/web/src/admin/crypto/CertificateKeyPairListPage.ts +++ b/web/src/admin/crypto/CertificateKeyPairListPage.ts @@ -6,6 +6,7 @@ import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -17,7 +18,11 @@ import { customElement, property } from "lit/decorators.js"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; -import { CertificateKeyPair, CryptoApi } from "@goauthentik/api"; +import { + CertificateKeyPair, + CryptoApi, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; @customElement("ak-crypto-certificate-list") export class CertificateKeyPairListPage extends TablePage { @@ -119,16 +124,21 @@ export class CertificateKeyPairListPage extends TablePage { `, html` ${item.certExpiry?.toLocaleString()} `, html` - ${msg("Update")} - ${msg("Update Certificate-Key Pair")} - - - - `, + ${msg("Update")} + ${msg("Update Certificate-Key Pair")} + + + + + + `, ]; } diff --git a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts index 27a0c1b67..0d2f16cf3 100644 --- a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts +++ b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts @@ -7,6 +7,7 @@ import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/cards/AggregateCard"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -23,7 +24,13 @@ import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; -import { EnterpriseApi, License, LicenseForecast, LicenseSummary } from "@goauthentik/api"; +import { + EnterpriseApi, + License, + LicenseForecast, + LicenseSummary, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; @customElement("ak-enterprise-license-list") export class EnterpriseLicenseListPage extends TablePage { @@ -221,16 +228,21 @@ export class EnterpriseLicenseListPage extends TablePage {
${msg(str`External: ${item.externalUsers}`)}
`, html` ${item.expiry?.toLocaleString()} `, html` - ${msg("Update")} - ${msg("Update License")} - - - - `, + ${msg("Update")} + ${msg("Update License")} + + + + + + `, ]; } diff --git a/web/src/admin/events/RuleListPage.ts b/web/src/admin/events/RuleListPage.ts index 58d3e3c1b..e997903b1 100644 --- a/web/src/admin/events/RuleListPage.ts +++ b/web/src/admin/events/RuleListPage.ts @@ -6,6 +6,8 @@ import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -15,7 +17,11 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { EventsApi, NotificationRule } from "@goauthentik/api"; +import { + EventsApi, + NotificationRule, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; @customElement("ak-event-rule-list") export class RuleListPage extends TablePage { @@ -88,15 +94,21 @@ export class RuleListPage extends TablePage { ? html`${item.groupObj.name}` : msg("None (rule disabled)")}`, html` - ${msg("Update")} - ${msg("Update Notification Rule")} - - - `, + ${msg("Update")} + ${msg("Update Notification Rule")} + + + + + + `, ]; } diff --git a/web/src/admin/events/TransportListPage.ts b/web/src/admin/events/TransportListPage.ts index 07f8bc57f..c36c21af9 100644 --- a/web/src/admin/events/TransportListPage.ts +++ b/web/src/admin/events/TransportListPage.ts @@ -5,6 +5,8 @@ import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -14,7 +16,11 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { EventsApi, NotificationTransport } from "@goauthentik/api"; +import { + EventsApi, + NotificationTransport, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; @customElement("ak-event-transport-list") export class TransportListPage extends TablePage { @@ -90,6 +96,12 @@ export class TransportListPage extends TablePage { + + + { diff --git a/web/src/admin/flows/FlowViewPage.ts b/web/src/admin/flows/FlowViewPage.ts index edd752c7f..c760821e5 100644 --- a/web/src/admin/flows/FlowViewPage.ts +++ b/web/src/admin/flows/FlowViewPage.ts @@ -3,6 +3,7 @@ import "@goauthentik/admin/flows/FlowDiagram"; import "@goauthentik/admin/flows/FlowForm"; import "@goauthentik/admin/policies/BoundPoliciesList"; import { DesignationToLabel } from "@goauthentik/app/admin/flows/utils"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/components/events/ObjectChangelog"; import { AKElement } from "@goauthentik/elements/Base"; @@ -22,7 +23,12 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Flow, FlowsApi, ResponseError } from "@goauthentik/api"; +import { + Flow, + FlowsApi, + RbacPermissionsAssignedByUsersListModelEnum, + ResponseError, +} from "@goauthentik/api"; @customElement("ak-flow-view") export class FlowViewPage extends AKElement { @@ -267,6 +273,12 @@ export class FlowViewPage extends AKElement { + `; } } diff --git a/web/src/admin/groups/GroupForm.ts b/web/src/admin/groups/GroupForm.ts index 6525d1aa8..2a1cca350 100644 --- a/web/src/admin/groups/GroupForm.ts +++ b/web/src/admin/groups/GroupForm.ts @@ -11,13 +11,22 @@ import YAML from "yaml"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement } from "lit/decorators.js"; +import { customElement, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { CoreApi, CoreGroupsListRequest, Group } from "@goauthentik/api"; +import { + CoreApi, + CoreGroupsListRequest, + Group, + PaginatedRoleList, + RbacApi, +} from "@goauthentik/api"; @customElement("ak-group-form") export class GroupForm extends ModelForm { + @state() + roles?: PaginatedRoleList; + static get styles(): CSSResult[] { return super.styles.concat(css` .pf-c-button.pf-m-control { @@ -43,6 +52,12 @@ export class GroupForm extends ModelForm { } } + async load(): Promise { + this.roles = await new RbacApi(DEFAULT_CONFIG).rbacRolesList({ + ordering: "name", + }); + } + async send(data: Group): Promise { if (this.instance?.pk) { return new CoreApi(DEFAULT_CONFIG).coreGroupsPartialUpdate({ @@ -112,6 +127,26 @@ export class GroupForm extends ModelForm { > + + +

+ ${msg( + "Select roles to grant this groups' users' permissions from the selected roles.", + )} +

+

+ ${msg("Hold control/command to select multiple items.")} +

+
${msg("Group Info")}
-
+
+
+
+ ${msg("Roles")} +
+
+
+
    + ${this.group.rolesObj.map((role) => { + return html`
  • + ${role.name} + +
  • `; + })} +
+
+
+
+ + `; } } diff --git a/web/src/admin/outposts/OutpostListPage.ts b/web/src/admin/outposts/OutpostListPage.ts index 575319f16..390134ad0 100644 --- a/web/src/admin/outposts/OutpostListPage.ts +++ b/web/src/admin/outposts/OutpostListPage.ts @@ -10,6 +10,7 @@ import { PFSize } from "@goauthentik/elements/Spinner"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -23,7 +24,13 @@ import { ifDefined } from "lit/directives/if-defined.js"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; -import { Outpost, OutpostHealth, OutpostTypeEnum, OutpostsApi } from "@goauthentik/api"; +import { + Outpost, + OutpostHealth, + OutpostTypeEnum, + OutpostsApi, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; export function TypeToLabel(type?: OutpostTypeEnum): string { if (!type) return ""; @@ -141,6 +148,11 @@ export class OutpostListPage extends TablePage { + + ${item.managed !== "goauthentik.io/outposts/embedded" ? html` - `, + html` + + ${msg("Update")} + ${msg(str`Update ${item.verboseName}`)} + + + + + + + `, ]; } diff --git a/web/src/admin/policies/PolicyListPage.ts b/web/src/admin/policies/PolicyListPage.ts index a467f94b4..9a1dda215 100644 --- a/web/src/admin/policies/PolicyListPage.ts +++ b/web/src/admin/policies/PolicyListPage.ts @@ -13,6 +13,7 @@ import "@goauthentik/elements/forms/ConfirmationForm"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import "@goauthentik/elements/forms/ProxyForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -92,6 +93,9 @@ export class PolicyListPage extends TablePage { + + + ${msg("Test")} ${msg("Test Policy")} diff --git a/web/src/admin/policies/reputation/ReputationListPage.ts b/web/src/admin/policies/reputation/ReputationListPage.ts index c2144a436..8c25f1514 100644 --- a/web/src/admin/policies/reputation/ReputationListPage.ts +++ b/web/src/admin/policies/reputation/ReputationListPage.ts @@ -4,6 +4,7 @@ import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -13,7 +14,11 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { PoliciesApi, Reputation } from "@goauthentik/api"; +import { + PoliciesApi, + RbacPermissionsAssignedByUsersListModelEnum, + Reputation, +} from "@goauthentik/api"; @customElement("ak-policy-reputation-list") export class ReputationListPage extends TablePage { @@ -52,6 +57,7 @@ export class ReputationListPage extends TablePage { new TableColumn(msg("IP"), "ip"), new TableColumn(msg("Score"), "score"), new TableColumn(msg("Updated"), "updated"), + new TableColumn(msg("Actions")), ]; } @@ -86,6 +92,13 @@ export class ReputationListPage extends TablePage { ${item.ip}`, html`${item.score}`, html`${item.updated.toLocaleString()}`, + html` + + + `, ]; } } diff --git a/web/src/admin/property-mappings/PropertyMappingListPage.ts b/web/src/admin/property-mappings/PropertyMappingListPage.ts index 500525922..e961a744c 100644 --- a/web/src/admin/property-mappings/PropertyMappingListPage.ts +++ b/web/src/admin/property-mappings/PropertyMappingListPage.ts @@ -10,6 +10,7 @@ import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import "@goauthentik/elements/forms/ProxyForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; @@ -107,6 +108,8 @@ export class PropertyMappingListPage extends TablePage { + + ${msg("Test")} ${msg("Test Property Mapping")} diff --git a/web/src/admin/providers/ProviderListPage.ts b/web/src/admin/providers/ProviderListPage.ts index c82985815..7dbab4f16 100644 --- a/web/src/admin/providers/ProviderListPage.ts +++ b/web/src/admin/providers/ProviderListPage.ts @@ -1,3 +1,4 @@ +import "@goauthentik/admin/applications/ApplicationWizardHint"; import "@goauthentik/admin/providers/ProviderWizard"; import "@goauthentik/admin/providers/ldap/LDAPProviderForm"; import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; @@ -60,6 +61,10 @@ export class ProviderListPage extends TablePage { ]; } + renderSectionBefore(): TemplateResult { + return html``; + } + renderToolbarSelected(): TemplateResult { const disabled = this.selectedElements.length < 1; return html` + `; } diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts b/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts index 920e71ceb..afe5dfd58 100644 --- a/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts +++ b/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts @@ -33,6 +33,7 @@ import { OAuth2ProviderSetupURLs, PropertyMappingPreview, ProvidersApi, + RbacPermissionsAssignedByUsersListModelEnum, } from "@goauthentik/api"; @customElement("ak-provider-oauth2-view") @@ -128,6 +129,12 @@ export class OAuth2ProviderViewPage extends AKElement { + `; } diff --git a/web/src/admin/providers/proxy/ProxyProviderViewPage.ts b/web/src/admin/providers/proxy/ProxyProviderViewPage.ts index 2451cfa1e..a0ce2792d 100644 --- a/web/src/admin/providers/proxy/ProxyProviderViewPage.ts +++ b/web/src/admin/providers/proxy/ProxyProviderViewPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/providers/RelatedApplicationButton"; import "@goauthentik/admin/providers/proxy/ProxyProviderForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { convertToSlug } from "@goauthentik/common/utils"; @@ -39,7 +40,12 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { ProvidersApi, ProxyMode, ProxyProvider } from "@goauthentik/api"; +import { + ProvidersApi, + ProxyMode, + ProxyProvider, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; export function ModeToLabel(action?: ProxyMode): string { if (!action) return ""; @@ -208,6 +214,12 @@ export class ProxyProviderViewPage extends AKElement { + `; } diff --git a/web/src/admin/providers/radius/RadiusProviderForm.ts b/web/src/admin/providers/radius/RadiusProviderForm.ts index e38307074..3898ba048 100644 --- a/web/src/admin/providers/radius/RadiusProviderForm.ts +++ b/web/src/admin/providers/radius/RadiusProviderForm.ts @@ -70,6 +70,26 @@ export class RadiusProviderFormPage extends ModelForm { >

${msg("Flow used for users to authenticate.")}

+ + +

+ ${msg( + "When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.", + )} +

+
${msg("Protocol settings")} diff --git a/web/src/admin/providers/radius/RadiusProviderViewPage.ts b/web/src/admin/providers/radius/RadiusProviderViewPage.ts index b62600a97..3963face9 100644 --- a/web/src/admin/providers/radius/RadiusProviderViewPage.ts +++ b/web/src/admin/providers/radius/RadiusProviderViewPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/providers/RelatedApplicationButton"; import "@goauthentik/admin/providers/radius/RadiusProviderForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -21,10 +22,13 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; -import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css"; -import { ProvidersApi, RadiusProvider } from "@goauthentik/api"; +import { + ProvidersApi, + RadiusProvider, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; @customElement("ak-provider-radius-view") export class RadiusProviderViewPage extends AKElement { @@ -50,7 +54,6 @@ export class RadiusProviderViewPage extends AKElement { PFBase, PFButton, PFPage, - PFFlex, PFDisplay, PFGallery, PFContent, @@ -162,6 +165,12 @@ export class RadiusProviderViewPage extends AKElement { + `; } } diff --git a/web/src/admin/providers/saml/SAMLProviderViewPage.ts b/web/src/admin/providers/saml/SAMLProviderViewPage.ts index 3e8d773ef..806a51b6b 100644 --- a/web/src/admin/providers/saml/SAMLProviderViewPage.ts +++ b/web/src/admin/providers/saml/SAMLProviderViewPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/providers/RelatedApplicationButton"; import "@goauthentik/admin/providers/saml/SAMLProviderForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { MessageLevel } from "@goauthentik/common/messages"; @@ -34,6 +35,7 @@ import { CertificateKeyPair, CryptoApi, ProvidersApi, + RbacPermissionsAssignedByUsersListModelEnum, SAMLMetadata, SAMLProvider, } from "@goauthentik/api"; @@ -226,6 +228,12 @@ export class SAMLProviderViewPage extends AKElement { + `; } diff --git a/web/src/admin/providers/scim/SCIMProviderViewPage.ts b/web/src/admin/providers/scim/SCIMProviderViewPage.ts index 8a4d7fe0b..3998c7c81 100644 --- a/web/src/admin/providers/scim/SCIMProviderViewPage.ts +++ b/web/src/admin/providers/scim/SCIMProviderViewPage.ts @@ -1,4 +1,5 @@ import "@goauthentik/admin/providers/scim/SCIMProviderForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -26,7 +27,12 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { ProvidersApi, SCIMProvider, Task } from "@goauthentik/api"; +import { + ProvidersApi, + RbacPermissionsAssignedByUsersListModelEnum, + SCIMProvider, + Task, +} from "@goauthentik/api"; @customElement("ak-provider-scim-view") export class SCIMProviderViewPage extends AKElement { @@ -113,6 +119,12 @@ export class SCIMProviderViewPage extends AKElement { + `; } @@ -120,10 +132,7 @@ export class SCIMProviderViewPage extends AKElement { if (!this.provider) { return html``; } - return html`
- ${msg("SCIM provider is in preview.")} -
- ${!this.provider?.assignedBackchannelApplicationName + return html` ${!this.provider?.assignedBackchannelApplicationName ? html`
${msg( "Warning: Provider is not assigned to an application as backchannel provider.", diff --git a/web/src/admin/roles/RoleForm.ts b/web/src/admin/roles/RoleForm.ts new file mode 100644 index 000000000..48b886b82 --- /dev/null +++ b/web/src/admin/roles/RoleForm.ts @@ -0,0 +1,56 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/elements/chips/Chip"; +import "@goauthentik/elements/chips/ChipGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/SearchSelect"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { RbacApi, Role } from "@goauthentik/api"; + +@customElement("ak-role-form") +export class RoleForm extends ModelForm { + loadInstance(pk: string): Promise { + return new RbacApi(DEFAULT_CONFIG).rbacRolesRetrieve({ + uuid: pk, + }); + } + + getSuccessMessage(): string { + if (this.instance) { + return msg("Successfully updated role."); + } else { + return msg("Successfully created role."); + } + } + + async send(data: Role): Promise { + if (this.instance?.pk) { + return new RbacApi(DEFAULT_CONFIG).rbacRolesPartialUpdate({ + uuid: this.instance.pk, + patchedRoleRequest: data, + }); + } else { + return new RbacApi(DEFAULT_CONFIG).rbacRolesCreate({ + roleRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html`
+ + + +
`; + } +} diff --git a/web/src/admin/roles/RoleListPage.ts b/web/src/admin/roles/RoleListPage.ts new file mode 100644 index 000000000..2bf8bf64e --- /dev/null +++ b/web/src/admin/roles/RoleListPage.ts @@ -0,0 +1,98 @@ +import "@goauthentik/admin/roles/RoleForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@goauthentik/elements/forms/ModalForm"; +import { PaginatedResponse } from "@goauthentik/elements/table/Table"; +import { TableColumn } from "@goauthentik/elements/table/Table"; +import { TablePage } from "@goauthentik/elements/table/TablePage"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { RbacApi, Role } from "@goauthentik/api"; + +@customElement("ak-role-list") +export class RoleListPage extends TablePage { + checkbox = true; + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return msg("Roles"); + } + pageDescription(): string { + return msg("Manage roles which grant permissions to objects within authentik."); + } + pageIcon(): string { + return "fa fa-lock"; + } + + @property() + order = "name"; + + async apiEndpoint(page: number): Promise> { + return new RbacApi(DEFAULT_CONFIG).rbacRolesList({ + ordering: this.order, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Name"), "name"), new TableColumn(msg("Actions"))]; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new RbacApi(DEFAULT_CONFIG).rbacRolesUsedByList({ + uuid: item.pk, + }); + }} + .delete=${(item: Role) => { + return new RbacApi(DEFAULT_CONFIG).rbacRolesDestroy({ + uuid: item.pk, + }); + }} + > + + `; + } + + row(item: Role): TemplateResult[] { + return [ + html`${item.name}`, + html` + ${msg("Update")} + ${msg("Update Role")} + + + `, + ]; + } + + renderObjectCreate(): TemplateResult { + return html` + + ${msg("Create")} + ${msg("Create Role")} + + + + `; + } +} diff --git a/web/src/admin/roles/RolePermissionForm.ts b/web/src/admin/roles/RolePermissionForm.ts new file mode 100644 index 000000000..f0312706a --- /dev/null +++ b/web/src/admin/roles/RolePermissionForm.ts @@ -0,0 +1,88 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-toggle-group"; +import "@goauthentik/elements/chips/Chip"; +import "@goauthentik/elements/chips/ChipGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/Radio"; +import "@goauthentik/elements/forms/SearchSelect"; +import "@goauthentik/elements/rbac/PermissionSelectModal"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import { Permission, RbacApi } from "@goauthentik/api"; + +interface RolePermissionAssign { + permissions: string[]; +} + +@customElement("ak-role-permission-form") +export class RolePermissionForm extends ModelForm { + @state() + permissionsToAdd: Permission[] = []; + + @property() + roleUuid?: string; + + async load(): Promise {} + + loadInstance(): Promise { + throw new Error("Method not implemented."); + } + + getSuccessMessage(): string { + return msg("Successfully assigned permission."); + } + + async send(data: RolePermissionAssign): Promise { + await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesAssignCreate({ + uuid: this.roleUuid || "", + permissionAssignRequest: { + permissions: data.permissions, + }, + }); + this.permissionsToAdd = []; + return; + } + + renderForm(): TemplateResult { + return html`
+ +
+ { + this.permissionsToAdd = items; + this.requestUpdate(); + return Promise.resolve(); + }} + > + + +
+ + ${this.permissionsToAdd.map((permission) => { + return html` { + const idx = this.permissionsToAdd.indexOf(permission); + this.permissionsToAdd.splice(idx, 1); + this.requestUpdate(); + }} + > + ${permission.name} + `; + })} + +
+
+
+
`; + } +} diff --git a/web/src/admin/roles/RolePermissionGlobalTable.ts b/web/src/admin/roles/RolePermissionGlobalTable.ts new file mode 100644 index 000000000..9a302c19c --- /dev/null +++ b/web/src/admin/roles/RolePermissionGlobalTable.ts @@ -0,0 +1,89 @@ +import "@goauthentik/admin/roles/RolePermissionForm"; +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; +import { groupBy } from "@goauthentik/app/common/utils"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import "@goauthentik/elements/forms/ModalForm"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { Permission, RbacApi } from "@goauthentik/api"; + +@customElement("ak-role-permissions-global-table") +export class RolePermissionGlobalTable extends Table { + @property() + roleUuid?: string; + + searchEnabled(): boolean { + return true; + } + + checkbox = true; + + order = "content_type__app_label,content_type__model"; + + apiEndpoint(page: number): Promise> { + return new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ + role: this.roleUuid, + page: page, + ordering: this.order, + search: this.search, + }); + } + + groupBy(items: Permission[]): [string, Permission[]][] { + return groupBy(items, (obj) => { + return obj.appLabelVerbose; + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Model", "model"), + new TableColumn("Permission", ""), + new TableColumn(""), + ]; + } + + renderObjectCreate(): TemplateResult { + return html` + + ${msg("Assign")} + ${msg("Assign permission to role")} + + + + + `; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new RbacApi( + DEFAULT_CONFIG, + ).rbacPermissionsAssignedByRolesUnassignPartialUpdate({ + uuid: this.roleUuid || "", + patchedPermissionAssignRequest: { + permissions: [`${item.appLabel}.${item.codename}`], + }, + }); + }} + > + + `; + } + + row(item: Permission): TemplateResult[] { + return [html`${item.modelVerbose}`, html`${item.name}`, html`✓`]; + } +} diff --git a/web/src/admin/roles/RolePermissionObjectTable.ts b/web/src/admin/roles/RolePermissionObjectTable.ts new file mode 100644 index 000000000..44bdf1183 --- /dev/null +++ b/web/src/admin/roles/RolePermissionObjectTable.ts @@ -0,0 +1,95 @@ +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; +import { groupBy } from "@goauthentik/app/common/utils"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { ExtraRoleObjectPermission, ModelEnum, RbacApi } from "@goauthentik/api"; + +@customElement("ak-role-permissions-object-table") +export class RolePermissionObjectTable extends Table { + @property() + roleUuid?: string; + + searchEnabled(): boolean { + return true; + } + + checkbox = true; + + apiEndpoint(page: number): Promise> { + return new RbacApi(DEFAULT_CONFIG).rbacPermissionsRolesList({ + uuid: this.roleUuid || "", + page: page, + ordering: this.order, + search: this.search, + }); + } + + groupBy(items: ExtraRoleObjectPermission[]): [string, ExtraRoleObjectPermission[]][] { + return groupBy(items, (obj) => { + return obj.appLabelVerbose; + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Model", "model"), + new TableColumn("Permission", ""), + new TableColumn("Object", ""), + new TableColumn(""), + ]; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return [ + { key: msg("Permission"), value: item.name }, + { key: msg("Object"), value: item.objectDescription || item.objectPk }, + ]; + }} + .delete=${(item: ExtraRoleObjectPermission) => { + return new RbacApi( + DEFAULT_CONFIG, + ).rbacPermissionsAssignedByRolesUnassignPartialUpdate({ + uuid: this.roleUuid || "", + patchedPermissionAssignRequest: { + permissions: [`${item.appLabel}.${item.codename}`], + objectPk: item.objectPk, + model: `${item.appLabel}.${item.model}` as ModelEnum, + }, + }); + }} + > + + `; + } + + row(item: ExtraRoleObjectPermission): TemplateResult[] { + return [ + html`${item.modelVerbose}`, + html`${item.name}`, + html`${item.objectDescription + ? html`${item.objectDescription}` + : html` +
${item.objectPk}
+
`}`, + html`✓`, + ]; + } +} diff --git a/web/src/admin/roles/RoleViewPage.ts b/web/src/admin/roles/RoleViewPage.ts new file mode 100644 index 000000000..b9a77fac4 --- /dev/null +++ b/web/src/admin/roles/RoleViewPage.ts @@ -0,0 +1,144 @@ +import "@goauthentik/admin/groups/RelatedGroupList"; +import "@goauthentik/app/admin/roles/RolePermissionGlobalTable"; +import "@goauthentik/app/admin/roles/RolePermissionObjectTable"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import "@goauthentik/components/events/ObjectChangelog"; +import "@goauthentik/components/events/UserEvents"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/PageHeader"; +import "@goauthentik/elements/Tabs"; + +import { msg, str } from "@lit/localize"; +import { CSSResult, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; + +import { RbacApi, RbacPermissionsAssignedByUsersListModelEnum, Role } from "@goauthentik/api"; + +@customElement("ak-role-view") +export class RoleViewPage extends AKElement { + @property({ type: String }) + set roleId(id: string) { + new RbacApi(DEFAULT_CONFIG) + .rbacRolesRetrieve({ + uuid: id, + }) + .then((role) => { + this._role = role; + }); + } + + @state() + _role?: Role; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFPage, + PFButton, + PFDisplay, + PFGrid, + PFContent, + PFCard, + PFDescriptionList, + css` + .pf-c-description-list__description ak-action-button { + margin-right: 6px; + margin-bottom: 6px; + } + .ak-button-collection { + max-width: 12em; + } + `, + ]; + } + + constructor() { + super(); + this.addEventListener(EVENT_REFRESH, () => { + if (!this._role?.pk) return; + this.roleId = this._role?.pk; + }); + } + + render(): TemplateResult { + return html` + + ${this.renderBody()}`; + } + + renderBody(): TemplateResult { + if (!this._role) { + return html``; + } + return html` +
+
+
+
${msg("Role Info")}
+
+
+
+
+ ${msg("Name")} +
+
+
+ ${this._role.name} +
+
+
+
+
+
+
+
${msg("Assigned global permissions")}
+
+ +
+
+
+
${msg("Assigned object permissions")}
+
+ +
+
+
+
+ +
`; + } +} diff --git a/web/src/admin/sources/ldap/LDAPSourceViewPage.ts b/web/src/admin/sources/ldap/LDAPSourceViewPage.ts index 2c74bc5f2..36129c3c4 100644 --- a/web/src/admin/sources/ldap/LDAPSourceViewPage.ts +++ b/web/src/admin/sources/ldap/LDAPSourceViewPage.ts @@ -1,4 +1,5 @@ import "@goauthentik/admin/sources/ldap/LDAPSourceForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -22,7 +23,13 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { LDAPSource, SourcesApi, Task, TaskStatusEnum } from "@goauthentik/api"; +import { + LDAPSource, + RbacPermissionsAssignedByUsersListModelEnum, + SourcesApi, + Task, + TaskStatusEnum, +} from "@goauthentik/api"; @customElement("ak-source-ldap-view") export class LDAPSourceViewPage extends AKElement { @@ -206,6 +213,12 @@ export class LDAPSourceViewPage extends AKElement {
+ `; } } diff --git a/web/src/admin/sources/oauth/OAuthSourceViewPage.ts b/web/src/admin/sources/oauth/OAuthSourceViewPage.ts index 3bfa5cdaf..f70c13038 100644 --- a/web/src/admin/sources/oauth/OAuthSourceViewPage.ts +++ b/web/src/admin/sources/oauth/OAuthSourceViewPage.ts @@ -1,6 +1,7 @@ import "@goauthentik/admin/policies/BoundPoliciesList"; import "@goauthentik/admin/sources/oauth/OAuthSourceDiagram"; import "@goauthentik/admin/sources/oauth/OAuthSourceForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -22,7 +23,12 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { OAuthSource, ProviderTypeEnum, SourcesApi } from "@goauthentik/api"; +import { + OAuthSource, + ProviderTypeEnum, + RbacPermissionsAssignedByUsersListModelEnum, + SourcesApi, +} from "@goauthentik/api"; export function ProviderToLabel(provider?: ProviderTypeEnum): string { switch (provider) { @@ -238,6 +244,12 @@ export class OAuthSourceViewPage extends AKElement { + `; } } diff --git a/web/src/admin/sources/plex/PlexSourceViewPage.ts b/web/src/admin/sources/plex/PlexSourceViewPage.ts index 51db79d27..88287a8b2 100644 --- a/web/src/admin/sources/plex/PlexSourceViewPage.ts +++ b/web/src/admin/sources/plex/PlexSourceViewPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/policies/BoundPoliciesList"; import "@goauthentik/admin/sources/plex/PlexSourceForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -21,7 +22,11 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { PlexSource, SourcesApi } from "@goauthentik/api"; +import { + PlexSource, + RbacPermissionsAssignedByUsersListModelEnum, + SourcesApi, +} from "@goauthentik/api"; @customElement("ak-source-plex-view") export class PlexSourceViewPage extends AKElement { @@ -131,6 +136,12 @@ export class PlexSourceViewPage extends AKElement { + `; } } diff --git a/web/src/admin/sources/saml/SAMLSourceViewPage.ts b/web/src/admin/sources/saml/SAMLSourceViewPage.ts index b85768242..56a8750c9 100644 --- a/web/src/admin/sources/saml/SAMLSourceViewPage.ts +++ b/web/src/admin/sources/saml/SAMLSourceViewPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/policies/BoundPoliciesList"; import "@goauthentik/admin/sources/saml/SAMLSourceForm"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -22,7 +23,12 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { SAMLMetadata, SAMLSource, SourcesApi } from "@goauthentik/api"; +import { + RbacPermissionsAssignedByUsersListModelEnum, + SAMLMetadata, + SAMLSource, + SourcesApi, +} from "@goauthentik/api"; @customElement("ak-source-saml-view") export class SAMLSourceViewPage extends AKElement { @@ -206,6 +212,12 @@ export class SAMLSourceViewPage extends AKElement { + `; } } diff --git a/web/src/admin/stages/StageListPage.ts b/web/src/admin/stages/StageListPage.ts index 3308fb50e..fb28cf42d 100644 --- a/web/src/admin/stages/StageListPage.ts +++ b/web/src/admin/stages/StageListPage.ts @@ -24,6 +24,7 @@ import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import "@goauthentik/elements/forms/ProxyForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -149,6 +150,8 @@ export class StageListPage extends TablePage { + + ${this.renderStageActions(item)}`, ]; } diff --git a/web/src/admin/stages/deny/DenyStageForm.ts b/web/src/admin/stages/deny/DenyStageForm.ts index 929496a78..340e911d2 100644 --- a/web/src/admin/stages/deny/DenyStageForm.ts +++ b/web/src/admin/stages/deny/DenyStageForm.ts @@ -39,7 +39,8 @@ export class DenyStageForm extends ModelForm { } renderForm(): TemplateResult { - return html` + return html` + ${msg( "Statically deny the flow. To use this stage effectively, disable *Evaluate when flow is planned* on the respective binding.", )} @@ -51,6 +52,23 @@ export class DenyStageForm extends ModelForm { class="pf-c-form-control" required /> - `; + + + ${msg("Stage-specific settings")} +
+ + +

+ ${msg("Message shown when this stage is run.")} +

+
+
+
+ `; } } diff --git a/web/src/admin/stages/invitation/InvitationListLink.ts b/web/src/admin/stages/invitation/InvitationListLink.ts index 6f96db4f5..a08033c87 100644 --- a/web/src/admin/stages/invitation/InvitationListLink.ts +++ b/web/src/admin/stages/invitation/InvitationListLink.ts @@ -9,7 +9,6 @@ import { until } from "lit/directives/until.js"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; -import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { Invitation, StagesApi } from "@goauthentik/api"; @@ -23,7 +22,7 @@ export class InvitationListLink extends AKElement { selectedFlow?: string; static get styles(): CSSResult[] { - return [PFBase, PFForm, PFFormControl, PFFlex, PFDescriptionList]; + return [PFBase, PFForm, PFFormControl, PFDescriptionList]; } renderLink(): string { diff --git a/web/src/admin/stages/invitation/InvitationListPage.ts b/web/src/admin/stages/invitation/InvitationListPage.ts index 41b81e5f1..2288b0691 100644 --- a/web/src/admin/stages/invitation/InvitationListPage.ts +++ b/web/src/admin/stages/invitation/InvitationListPage.ts @@ -7,6 +7,7 @@ import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -19,7 +20,12 @@ import { ifDefined } from "lit/directives/if-defined.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; -import { FlowDesignationEnum, Invitation, StagesApi } from "@goauthentik/api"; +import { + FlowDesignationEnum, + Invitation, + RbacPermissionsAssignedByUsersListModelEnum, + StagesApi, +} from "@goauthentik/api"; @customElement("ak-stage-invitation-list") export class InvitationListPage extends TablePage { @@ -124,15 +130,20 @@ export class InvitationListPage extends TablePage { html`${item.createdBy?.username}`, html`${item.expires?.toLocaleString() || msg("-")}`, html` - ${msg("Update")} - ${msg("Update Invitation")} - - - `, + ${msg("Update")} + ${msg("Update Invitation")} + + + + + `, ]; } diff --git a/web/src/admin/stages/prompt/PromptListPage.ts b/web/src/admin/stages/prompt/PromptListPage.ts index a67f566e1..c2b84a689 100644 --- a/web/src/admin/stages/prompt/PromptListPage.ts +++ b/web/src/admin/stages/prompt/PromptListPage.ts @@ -5,6 +5,7 @@ import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -14,7 +15,7 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { Prompt, StagesApi } from "@goauthentik/api"; +import { Prompt, RbacPermissionsAssignedByUsersListModelEnum, StagesApi } from "@goauthentik/api"; @customElement("ak-stage-prompt-list") export class PromptListPage extends TablePage { @@ -88,15 +89,20 @@ export class PromptListPage extends TablePage { return html`
  • ${stage.name}
  • `; })}`, html` - ${msg("Update")} - ${msg("Update Prompt")} - - - `, + ${msg("Update")} + ${msg("Update Prompt")} + + + + + `, ]; } diff --git a/web/src/admin/tenants/TenantListPage.ts b/web/src/admin/tenants/TenantListPage.ts index 560b0d097..2edaeb2d4 100644 --- a/web/src/admin/tenants/TenantListPage.ts +++ b/web/src/admin/tenants/TenantListPage.ts @@ -5,6 +5,7 @@ import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -14,7 +15,7 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { CoreApi, Tenant } from "@goauthentik/api"; +import { CoreApi, RbacPermissionsAssignedByUsersListModelEnum, Tenant } from "@goauthentik/api"; @customElement("ak-tenant-list") export class TenantListPage extends TablePage { @@ -85,15 +86,21 @@ export class TenantListPage extends TablePage { ${item._default ? msg("Yes") : msg("No")} `, html` - ${msg("Update")} - ${msg("Update Tenant")} - - - `, + ${msg("Update")} + ${msg("Update Tenant")} + + + + + + `, ]; } diff --git a/web/src/admin/tokens/TokenListPage.ts b/web/src/admin/tokens/TokenListPage.ts index ea6c979df..ec4d1018d 100644 --- a/web/src/admin/tokens/TokenListPage.ts +++ b/web/src/admin/tokens/TokenListPage.ts @@ -7,6 +7,7 @@ import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/TokenCopyButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -16,7 +17,12 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { CoreApi, IntentEnum, Token } from "@goauthentik/api"; +import { + CoreApi, + IntentEnum, + RbacPermissionsAssignedByUsersListModelEnum, + Token, +} from "@goauthentik/api"; @customElement("ak-token-list") export class TokenListPage extends TablePage { @@ -120,7 +126,19 @@ export class TokenListPage extends TablePage { ` - : html``} + : html` `} + + { } renderForm(): TemplateResult { - return html`${this.group?.isSuperuser ? html`` : html``} - -
    - { - this.usersToAdd = items; - this.requestUpdate(); - return Promise.resolve(); - }} - > - - -
    - - ${this.usersToAdd.map((user) => { - return html` { - const idx = this.usersToAdd.indexOf(user); - this.usersToAdd.splice(idx, 1); - this.requestUpdate(); - }} - > - ${UserOption(user)} - `; - })} - -
    + return html` +
    + { + this.usersToAdd = items; + this.requestUpdate(); + return Promise.resolve(); + }} + > + + +
    + + ${this.usersToAdd.map((user) => { + return html` { + const idx = this.usersToAdd.indexOf(user); + this.usersToAdd.splice(idx, 1); + this.requestUpdate(); + }} + > + ${UserOption(user)} + `; + })} +
    - `; +
    +
    `; } } diff --git a/web/src/admin/users/UserAssignedGlobalPermissionsTable.ts b/web/src/admin/users/UserAssignedGlobalPermissionsTable.ts new file mode 100644 index 000000000..99f171c81 --- /dev/null +++ b/web/src/admin/users/UserAssignedGlobalPermissionsTable.ts @@ -0,0 +1,88 @@ +import "@goauthentik/admin/users/UserPermissionForm"; +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; +import { groupBy } from "@goauthentik/app/common/utils"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@goauthentik/elements/forms/ModalForm"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { Permission, RbacApi } from "@goauthentik/api"; + +@customElement("ak-user-assigned-global-permissions-table") +export class UserAssignedGlobalPermissionsTable extends Table { + @property({ type: Number }) + userId?: number; + + checkbox = true; + + apiEndpoint(page: number): Promise> { + return new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ + user: this.userId || 0, + page: page, + ordering: this.order, + search: this.search, + }); + } + + groupBy(items: Permission[]): [string, Permission[]][] { + return groupBy(items, (obj) => { + return obj.appLabelVerbose; + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Model", "model"), + new TableColumn("Permission", ""), + new TableColumn(""), + ]; + } + + renderObjectCreate(): TemplateResult { + return html` + + ${msg("Assign")} + ${msg("Assign permission to user")} + + + + + `; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return [{ key: msg("Permission"), value: item.name }]; + }} + .delete=${(item: Permission) => { + return new RbacApi( + DEFAULT_CONFIG, + ).rbacPermissionsAssignedByUsersUnassignPartialUpdate({ + id: this.userId || 0, + patchedPermissionAssignRequest: { + permissions: [`${item.appLabel}.${item.codename}`], + }, + }); + }} + > + + `; + } + + row(item: Permission): TemplateResult[] { + return [html`${item.modelVerbose}`, html`${item.name}`, html`✓`]; + } +} diff --git a/web/src/admin/users/UserAssignedObjectPermissionsTable.ts b/web/src/admin/users/UserAssignedObjectPermissionsTable.ts new file mode 100644 index 000000000..8e63ae8bb --- /dev/null +++ b/web/src/admin/users/UserAssignedObjectPermissionsTable.ts @@ -0,0 +1,91 @@ +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; +import { groupBy } from "@goauthentik/app/common/utils"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { ExtraUserObjectPermission, ModelEnum, RbacApi } from "@goauthentik/api"; + +@customElement("ak-user-assigned-object-permissions-table") +export class UserAssignedObjectPermissionsTable extends Table { + @property({ type: Number }) + userId?: number; + + checkbox = true; + + apiEndpoint(page: number): Promise> { + return new RbacApi(DEFAULT_CONFIG).rbacPermissionsUsersList({ + userId: this.userId || 0, + page: page, + ordering: this.order, + search: this.search, + }); + } + + groupBy(items: ExtraUserObjectPermission[]): [string, ExtraUserObjectPermission[]][] { + return groupBy(items, (obj) => { + return obj.appLabelVerbose; + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Model", "model"), + new TableColumn("Permission", ""), + new TableColumn("Object", ""), + new TableColumn(""), + ]; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return [ + { key: msg("Permission"), value: item.name }, + { key: msg("Object"), value: item.objectDescription || item.objectPk }, + ]; + }} + .delete=${(item: ExtraUserObjectPermission) => { + return new RbacApi( + DEFAULT_CONFIG, + ).rbacPermissionsAssignedByUsersUnassignPartialUpdate({ + id: this.userId || 0, + patchedPermissionAssignRequest: { + permissions: [`${item.appLabel}.${item.codename}`], + objectPk: item.objectPk, + model: `${item.appLabel}.${item.model}` as ModelEnum, + }, + }); + }} + > + + `; + } + + row(item: ExtraUserObjectPermission): TemplateResult[] { + return [ + html`${item.modelVerbose}`, + html`${item.name}`, + html`${item.objectDescription + ? html`${item.objectDescription}` + : html` +
    ${item.objectPk}
    +
    `}`, + html`✓`, + ]; + } +} diff --git a/web/src/admin/users/UserDevicesList.ts b/web/src/admin/users/UserDevicesTable.ts similarity index 96% rename from web/src/admin/users/UserDevicesList.ts rename to web/src/admin/users/UserDevicesTable.ts index 6db5af610..b120c3265 100644 --- a/web/src/admin/users/UserDevicesList.ts +++ b/web/src/admin/users/UserDevicesTable.ts @@ -10,8 +10,8 @@ import { customElement, property } from "lit/decorators.js"; import { AuthenticatorsApi, Device } from "@goauthentik/api"; -@customElement("ak-user-device-list") -export class UserDeviceList extends Table { +@customElement("ak-user-device-table") +export class UserDeviceTable extends Table { @property({ type: Number }) userId?: number; diff --git a/web/src/admin/users/UserPermissionForm.ts b/web/src/admin/users/UserPermissionForm.ts new file mode 100644 index 000000000..1f89045e0 --- /dev/null +++ b/web/src/admin/users/UserPermissionForm.ts @@ -0,0 +1,88 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-toggle-group"; +import "@goauthentik/elements/chips/Chip"; +import "@goauthentik/elements/chips/ChipGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/Radio"; +import "@goauthentik/elements/forms/SearchSelect"; +import "@goauthentik/elements/rbac/PermissionSelectModal"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import { Permission, RbacApi } from "@goauthentik/api"; + +interface UserPermissionAssign { + permissions: string[]; +} + +@customElement("ak-user-permission-form") +export class UserPermissionForm extends ModelForm { + @state() + permissionsToAdd: Permission[] = []; + + @property({ type: Number }) + userId?: number; + + async load(): Promise {} + + loadInstance(): Promise { + throw new Error("Method not implemented."); + } + + getSuccessMessage(): string { + return msg("Successfully assigned permission."); + } + + async send(data: UserPermissionAssign): Promise { + await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersAssignCreate({ + id: this.userId || 0, + permissionAssignRequest: { + permissions: data.permissions, + }, + }); + this.permissionsToAdd = []; + return; + } + + renderForm(): TemplateResult { + return html`
    + +
    + { + this.permissionsToAdd = items; + this.requestUpdate(); + return Promise.resolve(); + }} + > + + +
    + + ${this.permissionsToAdd.map((permission) => { + return html` { + const idx = this.permissionsToAdd.indexOf(permission); + this.permissionsToAdd.splice(idx, 1); + this.requestUpdate(); + }} + > + ${permission.name} + `; + })} + +
    +
    +
    +
    `; + } +} diff --git a/web/src/admin/users/UserViewPage.ts b/web/src/admin/users/UserViewPage.ts index 0212f1336..c97a20298 100644 --- a/web/src/admin/users/UserViewPage.ts +++ b/web/src/admin/users/UserViewPage.ts @@ -3,7 +3,10 @@ import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserChart"; import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserPasswordForm"; +import "@goauthentik/app/admin/users/UserAssignedGlobalPermissionsTable"; +import "@goauthentik/app/admin/users/UserAssignedObjectPermissionsTable"; import { me } from "@goauthentik/app/common/users"; +import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { MessageLevel } from "@goauthentik/common/messages"; @@ -35,12 +38,17 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; -import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css"; -import { CapabilitiesEnum, CoreApi, SessionUser, User } from "@goauthentik/api"; +import { + CapabilitiesEnum, + CoreApi, + RbacPermissionsAssignedByUsersListModelEnum, + SessionUser, + User, +} from "@goauthentik/api"; -import "./UserDevicesList"; +import "./UserDevicesTable"; @customElement("ak-user-view") export class UserViewPage extends AKElement { @@ -68,7 +76,6 @@ export class UserViewPage extends AKElement { return [ PFBase, PFPage, - PFFlex, PFButton, PFDisplay, PFGrid, @@ -443,7 +450,35 @@ export class UserViewPage extends AKElement { >
    - + +
    +
    + + +
    +
    +
    +
    ${msg("Assigned global permissions")}
    +
    + + +
    +
    +
    +
    ${msg("Assigned object permissions")}
    +
    + + +
    diff --git a/web/src/common/errors.ts b/web/src/common/errors.ts index 7ef4a308c..ad6156bfa 100644 --- a/web/src/common/errors.ts +++ b/web/src/common/errors.ts @@ -1,3 +1,30 @@ +import { + GenericError, + GenericErrorFromJSON, + ResponseError, + ValidationError, + ValidationErrorFromJSON, +} from "@goauthentik/api"; + export class SentryIgnoredError extends Error {} export class NotFoundError extends Error {} export class RequestError extends Error {} + +export type APIErrorTypes = ValidationError | GenericError; + +export async function parseAPIError(error: Error): Promise { + if (!(error instanceof ResponseError)) { + return error; + } + if (error.response.status < 400 && error.response.status > 499) { + return error; + } + const body = await error.response.json(); + if (error.response.status === 400) { + return ValidationErrorFromJSON(body); + } + if (error.response.status === 403) { + return GenericErrorFromJSON(body); + } + return body; +} diff --git a/web/src/common/merge.ts b/web/src/common/merge.ts new file mode 100644 index 000000000..4e60e856c --- /dev/null +++ b/web/src/common/merge.ts @@ -0,0 +1,120 @@ +/** 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(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, input: Record) { + 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) { + const result = {}; + for (const source of sources) { + _merge(result, source); + } + return result; +} + +export default merge; diff --git a/web/src/common/users.ts b/web/src/common/users.ts index 5378236ce..293047572 100644 --- a/web/src/common/users.ts +++ b/web/src/common/users.ts @@ -45,6 +45,7 @@ export function me(): Promise { username: "", name: "", settings: {}, + systemPermissions: [], }, }; if (ex.response?.status === 401 || ex.response?.status === 403) { diff --git a/web/src/components/ak-hint/ShowHintController.ts b/web/src/components/ak-hint/ShowHintController.ts index b3eb1393c..d4f4ed0ae 100644 --- a/web/src/components/ak-hint/ShowHintController.ts +++ b/web/src/components/ak-hint/ShowHintController.ts @@ -27,18 +27,27 @@ export class ShowHintController implements ReactiveController { constructor(host: ShowHintControllerHost, hintToken: string) { (this.host = host).addController(this); this.hintToken = hintToken; - this.hideTheHint = this.hideTheHint.bind(this); + this.hide = this.hide.bind(this); + this.show = this.show.bind(this); } - hideTheHint() { + setTheHint(state: boolean = false) { window?.localStorage.setItem( LOCALSTORAGE_AUTHENTIK_KEY, JSON.stringify({ ...getCurrentStorageValue(), - [this.hintToken]: false, + [this.hintToken]: state, }), ); - this.host.showHint = false; + this.host.showHint = state; + } + + hide() { + this.setTheHint(false); + } + + show() { + this.setTheHint(true); } hostConnected() { @@ -54,7 +63,7 @@ export class ShowHintController implements ReactiveController { render() { return html`
    - ${msg( + ${msg( "Don't show this message again.", )}
    + extends AKElement + implements ReactiveControllerHost +{ + // prettier-ignore + static get styles() { return [PFBase, PFButton]; } + + @state() + steps: Step[] = []; + + @state() + currentStep = 0; + + /** + * A reference to the frame. Since the frame implements and inherits from ModalButton, + * you will need either a reference to or query to the frame in order to call + * `.close()` on it. + */ + frame: Ref = createRef(); + + get step() { + return this.steps[this.currentStep]; + } + + prompt = msg("Create"); + + header: string; + + description?: string; + + wizard: AkWizardController; + + constructor(prompt: string, header: string, description?: string) { + super(); + this.header = header; + this.prompt = prompt; + this.description = description; + this.wizard = new AkWizardController(this); + } + + /** + * Derive the labels used by the frame's Breadcrumbs display. + */ + get stepLabels(): WizardStepLabel[] { + let disabled = false; + return this.steps.map((step, index) => { + disabled = disabled || step.disabled; + return { + label: step.label, + active: index === this.currentStep, + index, + disabled, + }; + }); + } + + /** + * You should still consider overriding this if you need to consider details like "Is the step + * requested valid?" + */ + handleNav(stepId: number | undefined) { + if (stepId === undefined || this.steps[stepId] === undefined) { + throw new Error(`Attempt to navigate to undefined step: ${stepId}`); + } + this.currentStep = stepId; + this.requestUpdate(); + } + + close() { + throw new Error("This function must be overridden in the child class."); + } + + /** + * This is where all the business logic and special cases go. The Wizard Controller intercepts + * updates tagged `ak-wizard-update` and forwards the event content here. Business logic about + * "is the current step valid?" and "should the Next button be made enabled" are controlled + * here. (Any step implementing WizardStep can do it anyhow it pleases, putting "is the current + * form valid" and so forth into the step object itself.) + */ + handleUpdate(_detail: D) { + throw new Error("This function must be overridden in the child class."); + } + + render() { + return html` + + + + `; + } +} diff --git a/web/src/components/ak-wizard-main/AkWizardController.ts b/web/src/components/ak-wizard-main/AkWizardController.ts new file mode 100644 index 000000000..f5c2a23d3 --- /dev/null +++ b/web/src/components/ak-wizard-main/AkWizardController.ts @@ -0,0 +1,104 @@ +import { type ReactiveController } from "lit"; + +import { type AkWizard, type WizardNavCommand } from "./types"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isCustomEvent = (v: any): v is CustomEvent => + v instanceof CustomEvent && "detail" in v; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isNavEvent = (v: any): v is CustomEvent => + isCustomEvent(v) && "command" in v.detail; + +/** + * AkWizardController + * + * A ReactiveController that plugs into any wizard and provides a somewhat more convenient API for + * interacting with that wizard. It expects three different events from the wizard frame, each of + * which has a corresponding method that then forwards the necessary information to the host: + * + * - nav: A request to navigate to different step. Calls the host's `handleNav()` with the requested + step number. + * - update: A request to update the content of the current step. Forwarded to the host's + * `handleUpdate()` method. + * - close: A request to end the wizard interaction. Forwarded to the host's `close()` method. + * + */ + +export class AkWizardController implements ReactiveController { + private host: AkWizard; + + constructor(host: AkWizard) { + this.host = host; + this.handleNavRequest = this.handleNavRequest.bind(this); + this.handleUpdateRequest = this.handleUpdateRequest.bind(this); + host.addController(this); + } + + get maxStep() { + return this.host.steps.length - 1; + } + + get nextStep() { + return this.host.currentStep < this.maxStep ? this.host.currentStep + 1 : undefined; + } + + get backStep() { + return this.host.currentStep > 0 ? this.host.currentStep - 1 : undefined; + } + + get step() { + return this.host.steps[this.host.currentStep]; + } + + hostConnected() { + this.host.addEventListener("ak-wizard-nav", this.handleNavRequest); + this.host.addEventListener("ak-wizard-update", this.handleUpdateRequest); + this.host.addEventListener("ak-wizard-closed", this.handleCloseRequest); + } + + hostDisconnected() { + this.host.removeEventListener("ak-wizard-nav", this.handleNavRequest); + this.host.removeEventListener("ak-wizard-update", this.handleUpdateRequest); + this.host.removeEventListener("ak-wizard-closed", this.handleCloseRequest); + } + + handleNavRequest(event: Event) { + if (!isNavEvent(event)) { + throw new Error(`Unexpected event received by nav handler: ${event}`); + } + + if (event.detail.command === "close") { + this.host.close(); + return; + } + + const navigate = (): number | undefined => { + switch (event.detail.command) { + case "next": + return this.nextStep; + case "back": + return this.backStep; + case "goto": + return event.detail.step; + default: + throw new Error( + `Unrecognized command passed to ak-wizard-controller:handleNavRequest: ${event.detail.command}`, + ); + } + }; + + this.host.handleNav(navigate()); + } + + handleUpdateRequest(event: Event) { + if (!isCustomEvent(event)) { + throw new Error(`Unexpected event received by nav handler: ${event}`); + } + this.host.handleUpdate(event.detail); + } + + handleCloseRequest() { + this.host.close(); + } +} diff --git a/web/src/components/ak-wizard-main/ak-wizard-frame.ts b/web/src/components/ak-wizard-main/ak-wizard-frame.ts new file mode 100644 index 000000000..a0f320095 --- /dev/null +++ b/web/src/components/ak-wizard-main/ak-wizard-frame.ts @@ -0,0 +1,201 @@ +import { ModalButton } from "@goauthentik/elements/buttons/ModalButton"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { msg } from "@lit/localize"; +import { customElement, property, query } from "@lit/reactive-element/decorators.js"; +import { TemplateResult, html, nothing } from "lit"; +import { classMap } from "lit/directives/class-map.js"; +import { map } from "lit/directives/map.js"; + +import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css"; + +import { type WizardButton, WizardStepLabel } from "./types"; + +/** + * AKWizardFrame is the main container for displaying Wizard pages. + * + * AKWizardFrame is one component of a Wizard development environment. It provides the header, + * titled navigation sidebar, and bottom row button bar. It takes its cues about what to render from + * two data structure, `this.steps: WizardStep[]`, which lists all the current steps *in order* and + * doesn't care otherwise about their structure, and `this.currentStep: WizardStep` which must be a + * _reference_ to a member of `this.steps`. + * + * @element ak-wizard-frame + * + * @slot - Where the form itself should go + * + * @fires ak-wizard-nav - Tell the orchestrator what page the user wishes to move to. + * + */ + +@customElement("ak-wizard-frame") +export class AkWizardFrame extends CustomEmitterElement(ModalButton) { + static get styles() { + return [...super.styles, PFWizard]; + } + + /** + * The text for the title of the wizard + */ + @property() + header?: string; + + /** + * The text for a descriptive subtitle for the wizard + */ + @property() + description?: string; + + /** + * The labels for all current steps, including their availability + */ + @property({ attribute: false, type: Array }) + stepLabels!: WizardStepLabel[]; + + /** + * What buttons to Show + */ + @property({ attribute: false, type: Array }) + buttons: WizardButton[] = []; + + /** + * Show the [Cancel] icon and offer the [Cancel] button + */ + @property({ type: Boolean, attribute: "can-cancel" }) + canCancel = false; + + /** + * The form renderer, passed as a function + */ + @property({ type: Object }) + form!: () => TemplateResult; + + @query("#main-content *:first-child") + content!: HTMLElement; + + constructor() { + super(); + this.renderButtons = this.renderButtons.bind(this); + } + + renderModalInner() { + // prettier-ignore + return html`
    + ${this.renderHeader()} +
    +
    + ${this.renderNavigation()} + ${this.renderMainSection()} +
    + ${this.renderFooter()} +
    +
    `; + } + + renderHeader() { + return html`
    + ${this.canCancel ? this.renderHeaderCancelIcon() : nothing} +

    ${this.header}

    +

    ${this.description}

    +
    `; + } + + renderHeaderCancelIcon() { + return html``; + } + + renderNavigation() { + return html``; + } + + renderNavigationStep(step: WizardStepLabel) { + const buttonClasses = { + "pf-c-wizard__nav-link": true, + "pf-m-current": step.active, + }; + + return html` +
  • + +
  • + `; + } + + // This is where the panel is shown. We expect the panel to get its information from an + // independent context. + renderMainSection() { + return html`
    +
    ${this.form()}
    +
    `; + } + + renderFooter() { + return html` +
    ${map(this.buttons, this.renderButtons)}
    + `; + } + + renderButtons([label, command]: WizardButton) { + switch (command.command) { + case "next": + return this.renderButton(label, "pf-m-primary", command.command); + case "back": + return this.renderButton(label, "pf-m-secondary", command.command); + case "close": + return this.renderLink(label, "pf-m-link"); + default: + throw new Error(`Button type not understood: ${command} for ${label}`); + } + } + + renderButton(label: string, classname: string, command: string) { + const buttonClasses = { "pf-c-button": true, [classname]: true }; + return html``; + } + + renderLink(label: string, classname: string) { + const buttonClasses = { "pf-c-button": true, [classname]: true }; + return html``; + } +} + +export default AkWizardFrame; diff --git a/web/src/components/ak-wizard-main/commonWizardButtons.ts b/web/src/components/ak-wizard-main/commonWizardButtons.ts new file mode 100644 index 000000000..e9b531f36 --- /dev/null +++ b/web/src/components/ak-wizard-main/commonWizardButtons.ts @@ -0,0 +1,15 @@ +import { msg } from "@lit/localize"; + +import { WizardButton } from "./types"; + +export const NextStep: WizardButton = [msg("Next"), { command: "next" }]; + +export const BackStep: WizardButton = [msg("Back"), { command: "back" }]; + +export const SubmitStep: WizardButton = [msg("Submit"), { command: "next" }]; + +export const CancelWizard: WizardButton = [msg("Cancel"), { command: "close" }]; + +export const CloseWizard: WizardButton = [msg("Close"), { command: "close" }]; + +export const DisabledNextStep: WizardButton = [msg("Next"), { command: "next" }, true]; diff --git a/web/src/components/ak-wizard-main/stories/ak-demo-wizard.ts b/web/src/components/ak-wizard-main/stories/ak-demo-wizard.ts new file mode 100644 index 000000000..30322d666 --- /dev/null +++ b/web/src/components/ak-wizard-main/stories/ak-demo-wizard.ts @@ -0,0 +1,47 @@ +import type { WizardStep } from "@goauthentik/components/ak-wizard-main/types"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; +import { html } from "lit"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { AkWizard } from "../AkWizard"; +import { BackStep, CancelWizard, CloseWizard, NextStep } from "../commonWizardButtons"; + +type WizardStateUpdate = { + message: string; +}; + +const dummySteps: WizardStep[] = [ + { + label: "Test Step1", + render: () => html`

    This space intentionally left blank today

    `, + disabled: false, + buttons: [NextStep, CancelWizard], + }, + { + label: "Test Step 2", + render: () => html`

    This space also intentionally left blank

    `, + disabled: false, + buttons: [BackStep, CloseWizard], + }, +]; + +@customElement("ak-demo-wizard") +export class ApplicationWizard extends AkWizard { + static get styles() { + return [PFBase, PFButton, PFRadio]; + } + + constructor() { + super(msg("Open Wizard"), msg("Demo Wizard"), msg("Run the demo wizard")); + this.steps = [...dummySteps]; + } + + close() { + this.frame.value!.open = false; + } +} diff --git a/web/src/components/ak-wizard-main/stories/ak-wizard-main.stories.ts b/web/src/components/ak-wizard-main/stories/ak-wizard-main.stories.ts new file mode 100644 index 000000000..0de141dc2 --- /dev/null +++ b/web/src/components/ak-wizard-main/stories/ak-wizard-main.stories.ts @@ -0,0 +1,40 @@ +import type { WizardStep } from "@goauthentik/components/ak-wizard-main/types"; +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +import { AkWizard } from "../AkWizard"; +import "./ak-demo-wizard"; + +const metadata: Meta> = { + title: "Components / Wizard / Basic", + component: "ak-wizard-main", + parameters: { + docs: { + description: { + component: "A container for our wizard.", + }, + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
    + + + ${testItem} +
    `; + +export const OnePageWizard = () => { + return container(html` `); +}; diff --git a/web/src/components/ak-wizard-main/types.ts b/web/src/components/ak-wizard-main/types.ts new file mode 100644 index 000000000..bd29ca89b --- /dev/null +++ b/web/src/components/ak-wizard-main/types.ts @@ -0,0 +1,79 @@ +import { type LitElement, type ReactiveControllerHost, type TemplateResult } from "lit"; + +/** These are the navigation commands that the frame will send up to the controller. In the + * accompanying file, `./commonWizardButtons.ts`, you'll find a variety of Next, Back, Close, + * Cancel, and Submit buttons that can be used to send these, but these commands are also + * used by the breadcrumbs to hop around the wizard (if the wizard client so chooses to allow), + */ + +export type WizardNavCommand = + | { command: "next" } + | { command: "back" } + | { command: "close" } + | { command: "goto"; step: number }; + +/** + * The pattern for buttons being passed to the wizard. See `./commonWizardButtons.ts` for + * example implementations. The details are: Label, Command, and Disabled. + */ +export type WizardButton = [string, WizardNavCommand, boolean?]; + +/** + * Objects of this type are produced by the Controller, and are used in the Breadcrumbs to + * indicate the name of the step, whether or not it is the current step ("active"), and + * whether or not it is disabled. It is up to WizardClients to ensure that a step is + * not both "active" and "disabled". + */ + +export type WizardStepLabel = { + label: string; + index: number; + active: boolean; + disabled: boolean; +}; + +type LitControllerHost = ReactiveControllerHost & LitElement; + +export interface AkWizard extends LitControllerHost { + // Every wizard must provide a list of the steps to show. This list can change, but if it does, + // note that the *first* page must never change, and it's the responsibility of the developer to + // ensure that if the list changes that the currentStep points to the right place. + steps: WizardStep[]; + + // The index of the current step; + currentStep: number; + + // An accessor to the current step; + step: WizardStep; + + // Handle pressing the "close," "cancel," or "done" buttons. + close: () => void; + + // When a navigation event such as "next," "back," or "go to" (from the breadcrumbs) occurs. + handleNav: (_1: number | undefined) => void; + + // When a notification that the data on the live form has changed. + handleUpdate: (_1: D) => void; +} + +export interface WizardStep { + // The name of the step, as shown in the navigation. + label: string; + + // A function which returns the html for rendering the actual content of the step, its form and + // such. + render: () => TemplateResult; + + // A collection of buttons, in render order, that are to be shown in the button bar. If you can, + // always lead with the [Back] button and ensure it's in the same place every time. The + // controller's current behavior is to call the host's `handleNav()` command with the index of + // the requested step, or undefined if the command is nonsensical. + buttons: WizardButton[]; + + // If this step is "disabled," the prior step's next button will be disabled. + disabled: boolean; +} + +export interface WizardPanel extends HTMLElement { + validator?: () => boolean; +} diff --git a/web/src/elements/PageHeader.ts b/web/src/elements/PageHeader.ts index 1f7539085..3f187291b 100644 --- a/web/src/elements/PageHeader.ts +++ b/web/src/elements/PageHeader.ts @@ -13,7 +13,7 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; @@ -55,6 +55,7 @@ export class PageHeader extends AKElement { @property() description?: string; + @state() _header = ""; static get styles(): CSSResult[] { diff --git a/web/src/elements/ak-locale-context/ak-locale-context.ts b/web/src/elements/ak-locale-context/ak-locale-context.ts index af69fbf19..912d6b711 100644 --- a/web/src/elements/ak-locale-context/ak-locale-context.ts +++ b/web/src/elements/ak-locale-context/ak-locale-context.ts @@ -74,7 +74,7 @@ export class LocaleContext extends LitElement { console.warn(`Received a non-custom event at EVENT_LOCALE_REQUEST: ${ev}`); return; } - console.log("Locale update request received."); + console.debug("authentik/locale: Locale update request received."); this.updateLocale(ev.detail.locale); } diff --git a/web/src/elements/ak-locale-context/definitions.ts b/web/src/elements/ak-locale-context/definitions.ts index 63ec25025..e920e85b1 100644 --- a/web/src/elements/ak-locale-context/definitions.ts +++ b/web/src/elements/ak-locale-context/definitions.ts @@ -35,6 +35,11 @@ export { enLocale }; // - Text Label // - Locale loader. +// prettier-ignore +const debug: LocaleRow = [ + "pseudo-LOCALE", /^pseudo/i, () => msg("Pseudolocale (for testing)"), async () => await import("@goauthentik/locales/pseudo-LOCALE"), +]; + // prettier-ignore const LOCALE_TABLE: LocaleRow[] = [ ["en", /^en([_-]|$)/i, () => msg("English"), async () => await import("@goauthentik/locales/en")], @@ -46,6 +51,7 @@ const LOCALE_TABLE: LocaleRow[] = [ ["zh-Hant", /^zh[_-](HK|Hant)/i, () => msg("Chinese (traditional)"), async () => await import("@goauthentik/locales/zh-Hant")], ["zh_TW", /^zh[_-]TW$/i, () => msg("Taiwanese Mandarin"), async () => await import("@goauthentik/locales/zh_TW")], ["zh-Hans", /^zh(\b|_)/i, () => msg("Chinese (simplified)"), async () => await import("@goauthentik/locales/zh-Hans")], + debug ]; export const LOCALES: AkLocale[] = LOCALE_TABLE.map(([code, match, label, locale]) => ({ diff --git a/web/src/elements/charts/Chart.ts b/web/src/elements/charts/Chart.ts index eb92dbfc3..0501db22a 100644 --- a/web/src/elements/charts/Chart.ts +++ b/web/src/elements/charts/Chart.ts @@ -22,7 +22,7 @@ import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; import { property, state } from "lit/decorators.js"; -import { UiThemeEnum } from "@goauthentik/api"; +import { ResponseError, UiThemeEnum } from "@goauthentik/api"; Chart.register(Legend, Tooltip); Chart.register(LineController, BarController, DoughnutController); @@ -65,6 +65,9 @@ export abstract class AKChart extends AKElement { @state() chart?: Chart; + @state() + error?: ResponseError; + @property() centerText?: string; @@ -129,19 +132,23 @@ export abstract class AKChart extends AKElement { } firstUpdated(): void { - this.apiRequest().then((r) => { - const canvas = this.shadowRoot?.querySelector("canvas"); - if (!canvas) { - console.warn("Failed to get canvas element"); - return; - } - const ctx = canvas.getContext("2d"); - if (!ctx) { - console.warn("failed to get 2d context"); - return; - } - this.chart = this.configureChart(r, ctx); - }); + this.apiRequest() + .then((r) => { + const canvas = this.shadowRoot?.querySelector("canvas"); + if (!canvas) { + console.warn("Failed to get canvas element"); + return; + } + const ctx = canvas.getContext("2d"); + if (!ctx) { + console.warn("failed to get 2d context"); + return; + } + this.chart = this.configureChart(r, ctx); + }) + .catch((exc: ResponseError) => { + this.error = exc; + }); } getChartType(): string { @@ -204,7 +211,15 @@ export abstract class AKChart extends AKElement { render(): TemplateResult { return html`
    - ${this.chart ? html`` : html``} + ${this.error + ? html` + +

    ${this.error.response.statusText}

    +
    + ` + : html`${this.chart + ? html`` + : html``}`} ${this.centerText ? html` ${this.centerText} ` : html``}
    diff --git a/web/src/elements/forms/DeleteBulkForm.ts b/web/src/elements/forms/DeleteBulkForm.ts index 5d56f19c0..693184b71 100644 --- a/web/src/elements/forms/DeleteBulkForm.ts +++ b/web/src/elements/forms/DeleteBulkForm.ts @@ -20,7 +20,6 @@ type BulkDeleteMetadata = { key: string; value: string }[]; @customElement("ak-delete-objects-table") export class DeleteObjectsTable extends Table { - expandable = true; paginated = false; @property({ attribute: false }) @@ -70,6 +69,11 @@ export class DeleteObjectsTable extends Table { return html``; } + firstUpdated(): void { + this.expandable = this.usedBy !== undefined; + super.firstUpdated(); + } + renderExpanded(item: T): TemplateResult { const handler = async () => { if (!this.usedByData.has(item) && this.usedBy) { diff --git a/web/src/elements/rbac/ObjectPermissionModal.ts b/web/src/elements/rbac/ObjectPermissionModal.ts new file mode 100644 index 000000000..596b2b2c5 --- /dev/null +++ b/web/src/elements/rbac/ObjectPermissionModal.ts @@ -0,0 +1,74 @@ +import { AKElement } from "@goauthentik/app/elements/Base"; +import "@goauthentik/elements/forms/ModalForm"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { RbacPermissionsAssignedByUsersListModelEnum } from "@goauthentik/api"; + +/** + * This is a bit of a hack to get the viewport checking from ModelForm, + * even though we actually don't need a form here. + * #TODO: Rework this in the future + */ +@customElement("ak-rbac-object-permission-modal-form") +export class ObjectPermissionsPageForm extends ModelForm { + @property() + model?: RbacPermissionsAssignedByUsersListModelEnum; + + @property() + objectPk?: string | number; + + loadInstance(): Promise { + return Promise.resolve(); + } + send(): Promise { + return Promise.resolve(); + } + + renderForm(): TemplateResult { + return html` + `; + } +} + +@customElement("ak-rbac-object-permission-modal") +export class ObjectPermissionModal extends AKElement { + @property() + model?: RbacPermissionsAssignedByUsersListModelEnum; + + @property() + objectPk?: string | number; + + static get styles(): CSSResult[] { + return [PFBase, PFButton]; + } + + render(): TemplateResult { + return html` + + ${msg("Update Permissions")} + + + + `; + } +} diff --git a/web/src/elements/rbac/ObjectPermissionsPage.ts b/web/src/elements/rbac/ObjectPermissionsPage.ts new file mode 100644 index 000000000..de202ab34 --- /dev/null +++ b/web/src/elements/rbac/ObjectPermissionsPage.ts @@ -0,0 +1,69 @@ +import { AKElement } from "@goauthentik/app/elements/Base"; +import "@goauthentik/app/elements/rbac/RoleObjectPermissionTable"; +import "@goauthentik/app/elements/rbac/UserObjectPermissionTable"; +import "@goauthentik/elements/Tabs"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { RbacPermissionsAssignedByUsersListModelEnum } from "@goauthentik/api"; + +@customElement("ak-rbac-object-permission-page") +export class ObjectPermissionPage extends AKElement { + @property() + model?: RbacPermissionsAssignedByUsersListModelEnum; + + @property() + objectPk?: string | number; + + static get styles(): CSSResult[] { + return [PFBase, PFGrid, PFPage, PFCard]; + } + + render(): TemplateResult { + return html` +
    +
    +
    +
    User Object Permissions
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    Role Object Permissions
    +
    + + +
    +
    +
    +
    +
    `; + } +} diff --git a/web/src/elements/rbac/PermissionSelectModal.ts b/web/src/elements/rbac/PermissionSelectModal.ts new file mode 100644 index 000000000..648d88f17 --- /dev/null +++ b/web/src/elements/rbac/PermissionSelectModal.ts @@ -0,0 +1,95 @@ +import { groupBy } from "@goauthentik/app/common/utils"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import { PaginatedResponse } from "@goauthentik/elements/table/Table"; +import { TableColumn } from "@goauthentik/elements/table/Table"; +import { TableModal } from "@goauthentik/elements/table/TableModal"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; + +import { Permission, RbacApi } from "@goauthentik/api"; + +@customElement("ak-rbac-permission-select-table") +export class PermissionSelectModal extends TableModal { + checkbox = true; + checkboxChip = true; + + searchEnabled(): boolean { + return true; + } + + @property() + confirm!: (selectedItems: Permission[]) => Promise; + + order = "content_type__app_label,content_type__model"; + + static get styles(): CSSResult[] { + return super.styles.concat(PFBanner); + } + + async apiEndpoint(page: number): Promise> { + return new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ + ordering: this.order, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + search: this.search || "", + }); + } + + groupBy(items: Permission[]): [string, Permission[]][] { + return groupBy(items, (perm) => { + return perm.appLabelVerbose; + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Name"), "codename"), new TableColumn(msg("Model"), "")]; + } + + row(item: Permission): TemplateResult[] { + return [ + html`
    +
    ${item.name}
    +
    `, + html`${item.modelVerbose}`, + ]; + } + + renderSelectedChip(item: Permission): TemplateResult { + return html`${item.name}`; + } + + renderModalInner(): TemplateResult { + return html`
    +
    +

    ${msg("Select permissions to grant")}

    +
    +
    +
    ${this.renderTable()}
    +
    + { + return this.confirm(this.selectedElements).then(() => { + this.open = false; + }); + }} + class="pf-m-primary" + > + ${msg("Add")}   + { + this.open = false; + }} + class="pf-m-secondary" + > + ${msg("Cancel")} + +
    `; + } +} diff --git a/web/src/elements/rbac/RoleObjectPermissionForm.ts b/web/src/elements/rbac/RoleObjectPermissionForm.ts new file mode 100644 index 000000000..230d30a2e --- /dev/null +++ b/web/src/elements/rbac/RoleObjectPermissionForm.ts @@ -0,0 +1,107 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-toggle-group"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/Radio"; +import "@goauthentik/elements/forms/SearchSelect"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import { + ModelEnum, + PaginatedPermissionList, + RbacApi, + RbacRolesListRequest, + Role, +} from "@goauthentik/api"; + +interface RoleAssignData { + role: string; + permissions: { + [key: string]: boolean; + }; +} + +@customElement("ak-rbac-role-object-permission-form") +export class RoleObjectPermissionForm extends ModelForm { + @property() + model?: ModelEnum; + + @property() + objectPk?: string; + + @state() + modelPermissions?: PaginatedPermissionList; + + async load(): Promise { + const [appLabel, modelName] = (this.model || "").split("."); + this.modelPermissions = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ + contentTypeModel: modelName, + contentTypeAppLabel: appLabel, + ordering: "codename", + }); + } + + loadInstance(): Promise { + throw new Error("Method not implemented."); + } + + getSuccessMessage(): string { + return msg("Successfully assigned permission."); + } + + send(data: RoleAssignData): Promise { + return new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesAssignCreate({ + uuid: data.role, + permissionAssignRequest: { + permissions: Object.keys(data.permissions).filter((key) => data.permissions[key]), + model: this.model!, + objectPk: this.objectPk, + }, + }); + } + + renderForm(): TemplateResult { + if (!this.modelPermissions) { + return html``; + } + return html`
    + + => { + const args: RbacRolesListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const roles = await new RbacApi(DEFAULT_CONFIG).rbacRolesList(args); + return roles.results; + }} + .renderElement=${(role: Role): string => { + return role.name; + }} + .value=${(role: Role | undefined): string | undefined => { + return role?.pk; + }} + > + + + ${this.modelPermissions?.results.map((perm) => { + return html` + + `; + })} +
    `; + } +} diff --git a/web/src/elements/rbac/RoleObjectPermissionTable.ts b/web/src/elements/rbac/RoleObjectPermissionTable.ts new file mode 100644 index 000000000..ee3c2161b --- /dev/null +++ b/web/src/elements/rbac/RoleObjectPermissionTable.ts @@ -0,0 +1,129 @@ +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/RoleObjectPermissionForm"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + PaginatedPermissionList, + RbacApi, + RbacPermissionsAssignedByRolesListModelEnum, + RoleAssignedObjectPermission, +} from "@goauthentik/api"; + +@customElement("ak-rbac-role-object-permission-table") +export class RoleAssignedObjectPermissionTable extends Table { + @property() + model?: RbacPermissionsAssignedByRolesListModelEnum; + + @property() + objectPk?: string | number; + + @state() + modelPermissions?: PaginatedPermissionList; + + checkbox = true; + + async apiEndpoint(page: number): Promise> { + const perms = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesList({ + page: page, + // TODO: better default + model: this.model || RbacPermissionsAssignedByRolesListModelEnum.CoreUser, + objectPk: this.objectPk?.toString(), + }); + const [appLabel, modelName] = (this.model || "").split("."); + const modelPermissions = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ + contentTypeModel: modelName, + contentTypeAppLabel: appLabel, + ordering: "codename", + }); + modelPermissions.results = modelPermissions.results.filter((value) => { + return !value.codename.startsWith("add_"); + }); + this.modelPermissions = modelPermissions; + return perms; + } + + columns(): TableColumn[] { + const baseColumns = [new TableColumn("User", "user")]; + // We don't check pagination since models shouldn't need to have that many permissions? + this.modelPermissions?.results.forEach((perm) => { + baseColumns.push(new TableColumn(perm.name, perm.codename)); + }); + return baseColumns; + } + + renderObjectCreate(): TemplateResult { + return html` + ${msg("Assign")} + ${msg("Assign permission to role")} + + + + `; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return [{ key: msg("Permission"), value: item.name }]; + }} + .delete=${(item: RoleAssignedObjectPermission) => { + return new RbacApi( + DEFAULT_CONFIG, + ).rbacPermissionsAssignedByRolesUnassignPartialUpdate({ + uuid: item.rolePk, + patchedPermissionAssignRequest: { + objectPk: this.objectPk?.toString(), + model: this.model, + permissions: item.permissions.map((perm) => { + return `${perm.appLabel}.${perm.codename}`; + }), + }, + }); + }} + > + + `; + } + + row(item: RoleAssignedObjectPermission): TemplateResult[] { + const baseRow = [html` ${item.name}`]; + this.modelPermissions?.results.forEach((perm) => { + const granted = + item.permissions.filter((uperm) => uperm.codename === perm.codename).length > 0; + baseRow.push(html` + { + console.log(granted); + }} + class="pf-m-link" + > + ${granted + ? html`` + : html`X`} + + `); + }); + return baseRow; + } +} diff --git a/web/src/elements/rbac/UserObjectPermissionForm.ts b/web/src/elements/rbac/UserObjectPermissionForm.ts new file mode 100644 index 000000000..3b3b66402 --- /dev/null +++ b/web/src/elements/rbac/UserObjectPermissionForm.ts @@ -0,0 +1,111 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-toggle-group"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/Radio"; +import "@goauthentik/elements/forms/SearchSelect"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import { + CoreApi, + CoreUsersListRequest, + ModelEnum, + PaginatedPermissionList, + RbacApi, + User, +} from "@goauthentik/api"; + +interface UserAssignData { + user: number; + permissions: { + [key: string]: boolean; + }; +} + +@customElement("ak-rbac-user-object-permission-form") +export class UserObjectPermissionForm extends ModelForm { + @property() + model?: ModelEnum; + + @property() + objectPk?: string; + + @state() + modelPermissions?: PaginatedPermissionList; + + async load(): Promise { + const [appLabel, modelName] = (this.model || "").split("."); + this.modelPermissions = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ + contentTypeModel: modelName, + contentTypeAppLabel: appLabel, + ordering: "codename", + }); + } + + loadInstance(): Promise { + throw new Error("Method not implemented."); + } + + getSuccessMessage(): string { + return msg("Successfully assigned permission."); + } + + send(data: UserAssignData): Promise { + return new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersAssignCreate({ + id: data.user, + permissionAssignRequest: { + permissions: Object.keys(data.permissions).filter((key) => data.permissions[key]), + model: this.model!, + objectPk: this.objectPk!, + }, + }); + } + + renderForm(): TemplateResult { + if (!this.modelPermissions) { + return html``; + } + return html`
    + + => { + const args: CoreUsersListRequest = { + ordering: "username", + }; + if (query !== undefined) { + args.search = query; + } + const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args); + return users.results; + }} + .renderElement=${(user: User): string => { + return user.username; + }} + .renderDescription=${(user: User): TemplateResult => { + return html`${user.name}`; + }} + .value=${(user: User | undefined): number | undefined => { + return user?.pk; + }} + > + + + ${this.modelPermissions?.results.map((perm) => { + return html` + + `; + })} +
    `; + } +} diff --git a/web/src/elements/rbac/UserObjectPermissionTable.ts b/web/src/elements/rbac/UserObjectPermissionTable.ts new file mode 100644 index 000000000..a746447cc --- /dev/null +++ b/web/src/elements/rbac/UserObjectPermissionTable.ts @@ -0,0 +1,127 @@ +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/UserObjectPermissionForm"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + PaginatedPermissionList, + RbacApi, + RbacPermissionsAssignedByUsersListModelEnum, + UserAssignedObjectPermission, +} from "@goauthentik/api"; + +@customElement("ak-rbac-user-object-permission-table") +export class UserAssignedObjectPermissionTable extends Table { + @property() + model?: RbacPermissionsAssignedByUsersListModelEnum; + + @property() + objectPk?: string | number; + + @state() + modelPermissions?: PaginatedPermissionList; + + checkbox = true; + + async apiEndpoint(page: number): Promise> { + const perms = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersList({ + page: page, + // TODO: better default + model: this.model || RbacPermissionsAssignedByUsersListModelEnum.CoreUser, + objectPk: this.objectPk?.toString(), + }); + const [appLabel, modelName] = (this.model || "").split("."); + const modelPermissions = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ + contentTypeModel: modelName, + contentTypeAppLabel: appLabel, + ordering: "codename", + }); + modelPermissions.results = modelPermissions.results.filter((value) => { + return !value.codename.startsWith("add_"); + }); + this.modelPermissions = modelPermissions; + return perms; + } + + columns(): TableColumn[] { + const baseColumns = [new TableColumn("User", "user")]; + // We don't check pagination since models shouldn't need to have that many permissions? + this.modelPermissions?.results.forEach((perm) => { + baseColumns.push(new TableColumn(perm.name, perm.codename)); + }); + return baseColumns; + } + + renderObjectCreate(): TemplateResult { + return html` + ${msg("Assign")} + ${msg("Assign permission to user")} + + + + `; + } + + renderToolbarSelected(): TemplateResult { + const disabled = + this.selectedElements.length < 1 || + this.selectedElements.filter((item) => item.isSuperuser).length > 0; + return html` !item.isSuperuser)} + .metadata=${(item: UserAssignedObjectPermission) => { + return [{ key: msg("Permission"), value: item.name }]; + }} + .delete=${(item: UserAssignedObjectPermission) => { + if (item.isSuperuser) { + return Promise.resolve(); + } + return new RbacApi( + DEFAULT_CONFIG, + ).rbacPermissionsAssignedByUsersUnassignPartialUpdate({ + id: item.pk, + patchedPermissionAssignRequest: { + objectPk: this.objectPk?.toString(), + model: this.model, + permissions: item.permissions.map((perm) => { + return `${perm.appLabel}.${perm.codename}`; + }), + }, + }); + }} + > + + `; + } + + row(item: UserAssignedObjectPermission): TemplateResult[] { + const baseRow = [html` ${item.username} `]; + this.modelPermissions?.results.forEach((perm) => { + let cell = html`X`; + if (item.permissions.filter((uperm) => uperm.codename === perm.codename).length > 0) { + cell = html``; + } else if (item.isSuperuser) { + cell = html``; + } + baseRow.push(cell); + }); + return baseRow; + } +} diff --git a/web/src/elements/table/Table.ts b/web/src/elements/table/Table.ts index b9cf20b14..642af4801 100644 --- a/web/src/elements/table/Table.ts +++ b/web/src/elements/table/Table.ts @@ -1,3 +1,4 @@ +import { APIErrorTypes, parseAPIError } from "@goauthentik/app/common/errors"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { groupBy } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; @@ -148,7 +149,7 @@ export abstract class Table extends AKElement { expandedElements: T[] = []; @state() - hasError?: Error; + error?: APIErrorTypes; static get styles(): CSSResult[] { return [ @@ -191,7 +192,7 @@ export abstract class Table extends AKElement { this.isLoading = true; try { this.data = await this.apiEndpoint(this.page); - this.hasError = undefined; + this.error = undefined; this.page = this.data.pagination.current; const newSelected: T[] = []; const newExpanded: T[] = []; @@ -228,7 +229,7 @@ export abstract class Table extends AKElement { this.expandedElements = newExpanded; } catch (ex) { this.isLoading = false; - this.hasError = ex as Error; + this.error = await parseAPIError(ex as Error); } } @@ -249,25 +250,32 @@ export abstract class Table extends AKElement {
    ${inner ? inner - : html``} + : html`
    ${this.renderObjectCreate()}
    +
    `}
    `; } + renderObjectCreate(): TemplateResult { + return html``; + } + renderError(): TemplateResult { + if (!this.error) { + return html``; + } return html` - ${this.hasError instanceof ResponseError - ? html`
    ${this.hasError.message}
    ` - : html`
    ${this.hasError?.toString()}
    `} + ${this.error instanceof ResponseError + ? html`
    ${this.error.message}
    ` + : html`
    ${this.error.detail}
    `}
    `; } private renderRows(): TemplateResult[] | undefined { - if (this.hasError) { + if (this.error) { return [this.renderEmpty(this.renderError())]; } if (!this.data || this.isLoading) { @@ -277,7 +285,7 @@ export abstract class Table extends AKElement { return [this.renderEmpty()]; } const groupedResults = this.groupBy(this.data.results); - if (groupedResults.length === 1) { + if (groupedResults.length === 1 && groupedResults[0][0] === "") { return this.renderRowGroup(groupedResults[0][1]); } return groupedResults.map(([group, items]) => { @@ -397,14 +405,15 @@ export abstract class Table extends AKElement { } renderToolbar(): TemplateResult { - return html` { - return this.fetch(); - }} - class="pf-m-secondary" - > - ${msg("Refresh")}`; + return html` ${this.renderObjectCreate()} + { + return this.fetch(); + }} + class="pf-m-secondary" + > + ${msg("Refresh")}`; } renderToolbarSelected(): TemplateResult { @@ -419,18 +428,20 @@ export abstract class Table extends AKElement { if (!this.searchEnabled()) { return html``; } - return html` { - this.search = value; - this.fetch(); - updateURLParams({ - search: value, - }); - }} - > - `; + return html`
    + { + this.search = value; + this.fetch(); + updateURLParams({ + search: value, + }); + }} + > + +
    `; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -441,7 +452,7 @@ export abstract class Table extends AKElement { renderToolbarContainer(): TemplateResult { return html`
    -
    ${this.renderSearch()}
    + ${this.renderSearch()}
    ${this.renderToolbar()}
    ${this.renderToolbarAfter()}
    ${this.renderToolbarSelected()}
    diff --git a/web/src/elements/table/TablePage.ts b/web/src/elements/table/TablePage.ts index 8ca297fe6..d3f207622 100644 --- a/web/src/elements/table/TablePage.ts +++ b/web/src/elements/table/TablePage.ts @@ -70,14 +70,6 @@ export abstract class TablePage extends Table { `; } - renderObjectCreate(): TemplateResult { - return html``; - } - - renderToolbar(): TemplateResult { - return html`${this.renderObjectCreate()}${super.renderToolbar()}`; - } - render(): TemplateResult { return html` { @@ -24,7 +28,7 @@ export class ConsentStage extends BaseStage { if (permission.name === "") { return html``; diff --git a/web/src/user/UserInterface.ts b/web/src/user/UserInterface.ts index 0d83982a1..fbd21d347 100644 --- a/web/src/user/UserInterface.ts +++ b/web/src/user/UserInterface.ts @@ -175,6 +175,10 @@ export class UserInterface extends Interface { default: userDisplay = this.me.user.username; } + const canAccessAdmin = + this.me.user.isSuperuser || + // TODO: somehow add `access_admin_interface` to the API schema + this.me.user.systemPermissions.includes("access_admin_interface"); return html`
    @@ -280,7 +284,7 @@ export class UserInterface extends Interface {
    - ${this.me.user.isSuperuser + ${canAccessAdmin ? html`Slug Slug - - Internal application name, used in URLs. - Interner Applikationsname, wird in URLs verwendet. - Optionally enter a group name. Applications with identical groups are shown grouped together. Geben Sie optional einen Gruppennamen ein. Anwendungen in gleicher Gruppe werden gruppiert angezeigt. @@ -1419,9 +1415,6 @@ Select a provider that this application should use. - - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. @@ -1696,9 +1689,6 @@ NameID attribute - - SCIM provider is in preview. - Warning: Provider is not assigned to an application as backchannel provider. @@ -1712,32 +1702,9 @@ Run sync again Synchronisation erneut ausführen - - Application details - - - Create application - - - Additional UI settings - Weitere UI-Einstellungen - - - OAuth2/OIDC - Modern applications, APIs and Single-page applications. - - SAML - SAML - - - XML-based SSO standard. Use this if your application only supports SAML. - - - Legacy applications which don't natively support SSO. - LDAP LDAP @@ -1745,116 +1712,13 @@ Provide an LDAP interface for applications and users to authenticate against. - - Link - Link - - - Authentication method - - - LDAP details - LDAP-Details - - - Create service account - - - Create provider - Anbieter erstellen - - - Application Link - - - URL which will be opened when a user clicks on the application. - - - Method details - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - Web application - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - - - Single-page applications - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - Native application - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - - - API - API - - - Authentication without user interaction, or machine-to-machine authentication. - - - Application type - - - Flow used when users access this application. - - - Proxy details - Proxy-Details - - - External domain - - - External domain you will be accessing the domain from. - - - Import SAML Metadata - - - Import the metadata document of the applicaation you want to configure. - - - Manual configuration - - - Manually configure SAML - - - SAML details - SAML-Details - - - URL that authentik will redirect back to after successful authentication. - - - Import SAML metadata - New application - - Create a new application. - Applications Anwendungen - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - Externe Anwendungen, die Authentik als Identitätsanbieter verwenden und Protokolle wie OAuth2 und SAML verwenden. Hier werden alle Anwendungen angezeigt; auch diejenigen, auf die Sie keinen Zugriff haben. - Provider Type Anbietertyp @@ -5933,11 +5797,213 @@ Bindings to groups/users are checked against the user of the event. Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Custom attributes Don't show this message again. + + + Failed to fetch + + + Failed to fetch data. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + User Object Permissions + + + Role Object Permissions + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Update Permissions + + + Editing is disabled for managed tokens + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + Permission(s) + + + Permission + + + User doesn't have view permission so description cannot be retrieved. + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/en.xlf b/web/xliff/en.xlf index 98ce779f9..8f2f42b75 100644 --- a/web/xliff/en.xlf +++ b/web/xliff/en.xlf @@ -1475,10 +1475,6 @@ Slug Slug - - Internal application name, used in URLs. - Internal application name, used in URLs. - 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. @@ -1491,10 +1487,6 @@ Select a provider that this application should use. Select a provider that this application should use. - - Backchannel providers - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. Select backchannel providers which augment the functionality of the main provider. @@ -1787,10 +1779,6 @@ NameID attribute NameID attribute - - SCIM provider is in preview. - SCIM provider is in preview. - Warning: Provider is not assigned to an application as backchannel provider. Warning: Provider is not assigned to an application as backchannel provider. @@ -1807,38 +1795,10 @@ Run sync again Run sync again - - Application details - Application details - - - Create application - Create application - - - Additional UI settings - Additional UI settings - - - OAuth2/OIDC - OAuth2/OIDC - Modern applications, APIs and Single-page applications. Modern applications, APIs and Single-page applications. - - SAML - SAML - - - XML-based SSO standard. Use this if your application only supports SAML. - XML-based SSO standard. Use this if your application only supports SAML. - - - Legacy applications which don't natively support SSO. - Legacy applications which don't natively support SSO. - LDAP LDAP @@ -1847,142 +1807,14 @@ Provide an LDAP interface for applications and users to authenticate against. Provide an LDAP interface for applications and users to authenticate against. - - Link - Link - - - Authentication method - Authentication method - - - LDAP details - LDAP details - - - Create service account - Create service account - - - Create provider - Create provider - - - Application Link - Application Link - - - URL which will be opened when a user clicks on the application. - URL which will be opened when a user clicks on the application. - - - Method details - Method details - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - Web application - Web application - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - - - Single-page applications - Single-page applications - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - Native application - Native application - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - Applications which redirect users to a non-web callback (for example, Android, iOS) - - - API - API - - - Authentication without user interaction, or machine-to-machine authentication. - Authentication without user interaction, or machine-to-machine authentication. - - - Application type - Application type - - - Flow used when users access this application. - Flow used when users access this application. - - - Proxy details - Proxy details - - - External domain - External domain - - - External domain you will be accessing the domain from. - External domain you will be accessing the domain from. - - - Import SAML Metadata - Import SAML Metadata - - - Import the metadata document of the applicaation you want to configure. - Import the metadata document of the applicaation you want to configure. - - - Manual configuration - Manual configuration - - - Manually configure SAML - Manually configure SAML - - - SAML details - SAML details - - - URL that authentik will redirect back to after successful authentication. - URL that authentik will redirect back to after successful authentication. - - - Import SAML metadata - Import SAML metadata - New application New application - - Create a new application. - Create a new application. - Applications Applications - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - Provider Type Provider Type @@ -6247,11 +6079,213 @@ Bindings to groups/users are checked against the user of the event. Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Custom attributes Don't show this message again. + + + Failed to fetch + + + Failed to fetch data. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + User Object Permissions + + + Role Object Permissions + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Update Permissions + + + Editing is disabled for managed tokens + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + Permission(s) + + + Permission + + + User doesn't have view permission so description cannot be retrieved. + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/es.xlf b/web/xliff/es.xlf index b428ab068..08477da67 100644 --- a/web/xliff/es.xlf +++ b/web/xliff/es.xlf @@ -1378,10 +1378,6 @@ Slug babosa - - Internal application name, used in URLs. - Nombre de la aplicación interna, utilizado en las URL. - Optionally enter a group name. Applications with identical groups are shown grouped together. @@ -1392,9 +1388,6 @@ Select a provider that this application should use. - - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. @@ -1668,9 +1661,6 @@ NameID attribute - - SCIM provider is in preview. - Warning: Provider is not assigned to an application as backchannel provider. @@ -1684,30 +1674,9 @@ Run sync again Vuelve a ejecutar la sincronización - - Application details - - - Create application - - - Additional UI settings - - - OAuth2/OIDC - Modern applications, APIs and Single-page applications. - - SAML - - - XML-based SSO standard. Use this if your application only supports SAML. - - - Legacy applications which don't natively support SSO. - LDAP LDAP @@ -1715,111 +1684,13 @@ Provide an LDAP interface for applications and users to authenticate against. - - Link - - - Authentication method - - - LDAP details - - - Create service account - - - Create provider - Crear proveedor - - - Application Link - - - URL which will be opened when a user clicks on the application. - - - Method details - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - Web application - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - - - Single-page applications - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - Native application - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - - - API - - - Authentication without user interaction, or machine-to-machine authentication. - - - Application type - - - Flow used when users access this application. - - - Proxy details - - - External domain - - - External domain you will be accessing the domain from. - - - Import SAML Metadata - - - Import the metadata document of the applicaation you want to configure. - - - Manual configuration - - - Manually configure SAML - - - SAML details - - - URL that authentik will redirect back to after successful authentication. - - - Import SAML metadata - New application - - Create a new application. - Applications Aplicaciones - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - Aplicaciones externas que usan authentik como proveedor de identidad, utilizando protocolos como OAuth2 y SAML. Aquí se muestran todas las aplicaciones, incluso aquellas a las que no puede acceder. - Provider Type Tipo de proveedor @@ -5841,11 +5712,213 @@ Bindings to groups/users are checked against the user of the event. Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Custom attributes Don't show this message again. + + + Failed to fetch + + + Failed to fetch data. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + User Object Permissions + + + Role Object Permissions + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Update Permissions + + + Editing is disabled for managed tokens + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + Permission(s) + + + Permission + + + User doesn't have view permission so description cannot be retrieved. + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/fr.xlf b/web/xliff/fr.xlf index 0174261e0..ac905389c 100644 --- a/web/xliff/fr.xlf +++ b/web/xliff/fr.xlf @@ -1836,11 +1836,6 @@ Il y a jour(s) Slug Slug - - - Internal application name, used in URLs. - Nom de l'application interne, utilisé dans les URLs. - Optionally enter a group name. Applications with identical groups are shown grouped together. @@ -1856,11 +1851,6 @@ Il y a jour(s) Select a provider that this application should use. Sélectionnez un fournisseur que cette application doit utiliser. - - - Backchannel providers - Fournisseurs backchannel - Select backchannel providers which augment the functionality of the main provider. @@ -2226,11 +2216,6 @@ Il y a jour(s) NameID attribute Attribut NameID - - - SCIM provider is in preview. - Le fournisseur SCIM est en aperçu. - Warning: Provider is not assigned to an application as backchannel provider. @@ -2251,46 +2236,11 @@ Il y a jour(s) Run sync again Relancer la synchro - - - Application details - Détails de l'application - - - - Create application - Créer une application - - - - Additional UI settings - Paramètres d'interface additionnels - - - - OAuth2/OIDC - OAuth2/OIDC - Modern applications, APIs and Single-page applications. Applications modernes, API et applications à page unique. - - - SAML - SAML - - - - XML-based SSO standard. Use this if your application only supports SAML. - Norme SSO basée sur XML. Utilisez cette option si votre application ne soutient que SAML. - - - - Legacy applications which don't natively support SSO. - Les applications anciennes qui ne supportent pas nativement un SSO. - LDAP @@ -2301,176 +2251,16 @@ Il y a jour(s) Provide an LDAP interface for applications and users to authenticate against. Fournir une interface LDAP permettant aux applications et aux utilisateurs de s'authentifier. - - - Link - Lien - - - - Authentication method - Méthode d'authentification - - - - LDAP details - Détails LDAP - - - - Create service account - Créer un compte de service - - - - Create provider - Créer un fournisseur - - - - Application Link - Lien de l’application - - - - URL which will be opened when a user clicks on the application. - URL qui sera ouverte lorsqu'un utilisateur clique sur l'application. - - - - Method details - Détails de la méthode - - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - Cette configuration peut être utilisée pour s'authentifier auprès d'authentik avec d'autres API ou de manière programmatique. - - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - Par défaut, tous les comptes de services peuvent se connecter à cette application, tant qu'ils ont un jeton valide du type app-password. - - - - Web application - Application Web - - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - Applications qui s’occupent de l’authentification côté serveur (par exemple Python, Go, Rust, Java, PHP) - - - - Single-page applications - Applications à page unique - - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - Applications à page unique qui gèrent l'authentification dans le navigateur (par exemple, Javascript, Angular, React, Vue). - - - - Native application - Application native - - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - Applications qui redirigent les utilisateurs vers un callback non web (par exemple, Android, iOS) - - - - API - API - - - - Authentication without user interaction, or machine-to-machine authentication. - Authentification sans interaction avec l'utilisateur, ou authentification de machine à machine. - - - - Application type - Type d’application - - - - Flow used when users access this application. - Flux utilisé lorsque les utilisateurs accèdent à cette application. - - - - Proxy details - Détails du Proxy - - - - External domain - Domaine externe - - - - External domain you will be accessing the domain from. - Domaine externe à partir duquel vous accéderez au domaine. - - - - Import SAML Metadata - Importer des métadonnées SAML - - - - Import the metadata document of the applicaation you want to configure. - Importez le document de métadonnées de l'application que vous souhaitez configurer. - - - - Manual configuration - Configuration manuelle - - - - Manually configure SAML - Configurer SAML manuellement - - - - SAML details - Détails SAML - - - - URL that authentik will redirect back to after successful authentication. - URL vers laquelle authentik redirigera après une authentification réussie. - - - - Import SAML metadata - Importer des métadonnées SAML - New application Nouvelle application - - - Create a new application. - Créer une nouvelle application. - Applications Applications - - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - Applications externes qui utilisent authentik comme fournisseur d'identité, en utilisant des protocoles comme OAuth2 et SAML. Toutes les applications sont affichées ici, même celles auxquelles vous n'avez pas accéder. - Provider Type @@ -7809,24 +7599,268 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + Utilisez ce fournisseur avec l'option "auth_request" de Nginx ou "forwardAuth" de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, "/outpost.goauthentik.io" doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous). Default relay state + Relay state par défaut When using IDP-initiated logins, the relay state will be set to this value. + Lors de l'utilisation de connexions initiées par l'IdP, le relay state sera défini à cette valeur. Flow Info + Informations du flux Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). + Étape de configuration d'un authentificateur WebAuthn (Yubikey, FaceID/Windows Hello). + +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). Custom attributes + Attributs personnalisés Don't show this message again. + Ne plus montrer ce message. + + + Failed to fetch + Erreur de récupération + + + Failed to fetch data. + Erreur de récupération des données. + + + Successfully assigned permission. + Les permissions ont été assignées avec succès. + + + Role + Rôle + + + Assign + Assigner + + + Assign permission to role + Assigner une permission à un rôle + + + Assign to new role + Assigner à un nouveau rôle + + + Directly assigned + Assigné directement + + + Assign permission to user + Assigner une permission à un utilisateur + + + Assign to new user + Assigner à un nouvel utilisateur + + + User Object Permissions + Permissions de l'objet utilisateur + + + Role Object Permissions + Permission de l'objet rôle + + + Roles + Rôles + + + Select roles to grant this groups' users' permissions from the selected roles. + Sélectionner les roles depuis lesquels assigner les permissions des utilisateurs de ce groupe depuis les rôles sélectionnés. + + + Update Permissions + Mettre à jour les permissions + + + Editing is disabled for managed tokens + L'édition est désactivée pour les jetons gérés + + + Select permissions to grant + Sélectionner les permissions à attribuer + + + Permissions to add + Permissions à ajouter + + + Select permissions + Sélectionner les permissions + + + Assign permission + Assigner les permissions + + + Permission(s) + Permission(s) + + + Permission + Permission + + + User doesn't have view permission so description cannot be retrieved. + L'utilisateur n'a pas les permissions de lecture, la description ne peut donc pas être récupérée. + + + Assigned permissions + Permissions assignées + + + Assigned global permissions + Permissions globales assignées + + + Assigned object permissions + Permissions d'objet assignées + + + Successfully updated role. + Rôle mis à jour avec succès. + + + Successfully created role. + Rôle créé avec succès. + + + Manage roles which grant permissions to objects within authentik. + Gérer les rôles qui attribuent des permissions sur les objets au sein d'authentik. + + + Role(s) + Role(s) + + + Update Role + Mettre à jour le rôle + + + Create Role + Créer un rôle + + + Role doesn't have view permission so description cannot be retrieved. + Le rôle n'a pas les permissions de lecture, la description ne peut donc pas être récupérée. + + + Role + Rôle + + + Role Info + Informations du rôle + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/pl.xlf b/web/xliff/pl.xlf index ad3cb85ca..5938a0e53 100644 --- a/web/xliff/pl.xlf +++ b/web/xliff/pl.xlf @@ -1418,10 +1418,6 @@ Slug Ślimak - - Internal application name, used in URLs. - Wewnętrzna nazwa aplikacji, używana w adresach URL. - Optionally enter a group name. Applications with identical groups are shown grouped together. Opcjonalnie wprowadź nazwę grupy. Aplikacje z identycznymi grupami są wyświetlane razem. @@ -1433,9 +1429,6 @@ Select a provider that this application should use. - - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. @@ -1722,9 +1715,6 @@ NameID attribute Atrybut NameID - - SCIM provider is in preview. - Warning: Provider is not assigned to an application as backchannel provider. @@ -1738,38 +1728,10 @@ Run sync again Uruchom ponownie synchronizację - - Application details - Szczegóły aplikacji - - - Create application - Utwórz aplikację - - - Additional UI settings - Dodatkowe ustawienia interfejsu użytkownika - - - OAuth2/OIDC - OAuth2/OIDC - Modern applications, APIs and Single-page applications. Nowoczesne aplikacje, API i aplikacje jednostronicowe. - - SAML - SAML - - - XML-based SSO standard. Use this if your application only supports SAML. - Standard SSO oparty na XML. Użyj tego, jeśli Twoja aplikacja obsługuje tylko SAML. - - - Legacy applications which don't natively support SSO. - Starsze aplikacje, które nie obsługują natywnego logowania jednokrotnego. - LDAP LDAP @@ -1777,136 +1739,14 @@ Provide an LDAP interface for applications and users to authenticate against. - - Link - Link - - - Authentication method - Metoda Uwierzytelnienia - - - LDAP details - Szczegóły LDAP - - - Create service account - Utwórz konto usługi - - - Create provider - Utwórz dostawcę - - - Application Link - Link do aplikacji - - - URL which will be opened when a user clicks on the application. - - - Method details - Szczegóły metody - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - Web application - Aplikacja internetowa - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - Aplikacje obsługujące uwierzytelnianie po stronie serwera (na przykład Python, Go, Rust, Java, PHP) - - - Single-page applications - Aplikacje jednostronicowe - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - Native application - Natywna aplikacja - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - Aplikacje, które przekierowują użytkowników do nie internetowych callback (na przykład Android, iOS) - - - API - API - - - Authentication without user interaction, or machine-to-machine authentication. - Uwierzytelnianie bez interakcji użytkownika lub uwierzytelnianie między maszynami. - - - Application type - Typ aplikacji - - - Flow used when users access this application. - Przepływ używany, gdy użytkownicy uzyskują dostęp do tej aplikacji. - - - Proxy details - Dane proxy - - - External domain - Zewnętrzna domena - - - External domain you will be accessing the domain from. - Domena zewnętrzna, z której będziesz uzyskiwać dostęp do domeny. - - - Import SAML Metadata - Importuj Metadane SAML - - - Import the metadata document of the applicaation you want to configure. - - - Manual configuration - Ręczna konfiguracja - - - Manually configure SAML - Ręcznie skonfiguruj SAML - - - SAML details - Szczegóły SAML - - - URL that authentik will redirect back to after successful authentication. - - - Import SAML metadata - Importuj metadane SAML - New application Nowa aplikacja - - Create a new application. - Utwórz nową aplikację - Applications Aplikacje - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - Aplikacje zewnętrzne, które używają authentik jako dostawcy tożsamości, wykorzystując protokoły takie jak OAuth2 i SAML. Tutaj wyświetlane są wszystkie aplikacje, nawet te, do których nie masz dostępu. - Provider Type Typ dostawcy @@ -6080,11 +5920,213 @@ Bindings to groups/users are checked against the user of the event. Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Custom attributes Don't show this message again. + + + Failed to fetch + + + Failed to fetch data. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + User Object Permissions + + + Role Object Permissions + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Update Permissions + + + Editing is disabled for managed tokens + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + Permission(s) + + + Permission + + + User doesn't have view permission so description cannot be retrieved. + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/pseudo-LOCALE.xlf b/web/xliff/pseudo-LOCALE.xlf index 2eabcf7c5..66ae3312c 100644 --- a/web/xliff/pseudo-LOCALE.xlf +++ b/web/xliff/pseudo-LOCALE.xlf @@ -4,6190 +4,7827 @@ English + Ēńĝĺĩśĥ French + Ƒŕēńćĥ Turkish + Ţũŕķĩśĥ Spanish + Śƥàńĩśĥ Polish + Ƥōĺĩśĥ Taiwanese Mandarin + Ţàĩŵàńēśē Màńďàŕĩń Chinese (simplified) + Ćĥĩńēśē (śĩmƥĺĩƒĩēď) Chinese (traditional) + Ćĥĩńēśē (ţŕàďĩţĩōńàĺ) German + Ĝēŕmàń Loading... + Ĺōàďĩńĝ... Application + Àƥƥĺĩćàţĩōń Logins + Ĺōĝĩńś Show less + Śĥōŵ ĺēśś Show more + Śĥōŵ mōŕē UID + ŨĨĎ Name + Ńàmē App + Àƥƥ Model Name + Mōďēĺ Ńàmē Message + Mēśśàĝē Subject + ŚũƀĴēćţ From + Ƒŕōm To + Ţō Context + Ćōńţēxţ User + Ũśēŕ Affected model: + Àƒƒēćţēď mōďēĺ: Authorized application: + Àũţĥōŕĩźēď àƥƥĺĩćàţĩōń: Using flow + Ũśĩńĝ ƒĺōŵ Email info: + Ēmàĩĺ ĩńƒō: Secret: + Śēćŕēţ: Open issue on GitHub... + Ōƥēń ĩśśũē ōń ĜĩţĤũƀ... Exception + Ēxćēƥţĩōń Expression + Ēxƥŕēśśĩōń Binding + ßĩńďĩńĝ Request + Ŕēǫũēśţ Object + ŌƀĴēćţ Result + Ŕēśũĺţ Passing + Ƥàśśĩńĝ Messages + Mēśśàĝēś Using source + Ũśĩńĝ śōũŕćē Attempted to log in as + Àţţēmƥţēď ţō ĺōĝ ĩń àś No additional data available. + Ńō àďďĩţĩōńàĺ ďàţà àvàĩĺàƀĺē. Click to change value + Ćĺĩćķ ţō ćĥàńĝē vàĺũē Select an object. + Śēĺēćţ àń ōƀĴēćţ. Loading options... + Ĺōàďĩńĝ ōƥţĩōńś... Connection error, reconnecting... + Ćōńńēćţĩōń ēŕŕōŕ, ŕēćōńńēćţĩńĝ... Login + Ĺōĝĩń Failed login + Ƒàĩĺēď ĺōĝĩń Logout + Ĺōĝōũţ User was written to + Ũśēŕ ŵàś ŵŕĩţţēń ţō Suspicious request + Śũśƥĩćĩōũś ŕēǫũēśţ Password set + Ƥàśśŵōŕď śēţ Secret was viewed + Śēćŕēţ ŵàś vĩēŵēď Secret was rotated + Śēćŕēţ ŵàś ŕōţàţēď Invitation used + Ĩńvĩţàţĩōń ũśēď Application authorized + Àƥƥĺĩćàţĩōń àũţĥōŕĩźēď Source linked + Śōũŕćē ĺĩńķēď Impersonation started + Ĩmƥēŕśōńàţĩōń śţàŕţēď Impersonation ended + Ĩmƥēŕśōńàţĩōń ēńďēď Flow execution + Ƒĺōŵ ēxēćũţĩōń Policy execution + Ƥōĺĩćŷ ēxēćũţĩōń Policy exception + Ƥōĺĩćŷ ēxćēƥţĩōń Property Mapping exception + Ƥŕōƥēŕţŷ Màƥƥĩńĝ ēxćēƥţĩōń System task execution + Śŷśţēm ţàśķ ēxēćũţĩōń System task exception + Śŷśţēm ţàśķ ēxćēƥţĩōń General system exception + Ĝēńēŕàĺ śŷśţēm ēxćēƥţĩōń Configuration error + Ćōńƒĩĝũŕàţĩōń ēŕŕōŕ Model created + Mōďēĺ ćŕēàţēď Model updated + Mōďēĺ ũƥďàţēď Model deleted + Mōďēĺ ďēĺēţēď Email sent + Ēmàĩĺ śēńţ Update available + Ũƥďàţē àvàĩĺàƀĺē Unknown severity + Ũńķńōŵń śēvēŕĩţŷ Alert + Àĺēŕţ Notice + Ńōţĩćē Warning + Ŵàŕńĩńĝ no tabs defined + ńō ţàƀś ďēƒĩńēď - of + - ōƒ Go to previous page + Ĝō ţō ƥŕēvĩōũś ƥàĝē Go to next page + Ĝō ţō ńēxţ ƥàĝē Search... + Śēàŕćĥ... Loading + Ĺōàďĩńĝ No objects found. + Ńō ōƀĴēćţś ƒōũńď. Failed to fetch objects. + Ƒàĩĺēď ţō ƒēţćĥ ōƀĴēćţś. Refresh + Ŕēƒŕēśĥ Select all rows + Śēĺēćţ àĺĺ ŕōŵś Action + Àćţĩōń Creation Date + Ćŕēàţĩōń Ďàţē Client IP + Ćĺĩēńţ ĨƤ Tenant + Ţēńàńţ Recent events + Ŕēćēńţ ēvēńţś On behalf of + Ōń ƀēĥàĺƒ ōƒ - + - No Events found. + Ńō Ēvēńţś ƒōũńď. No matching events could be found. + Ńō màţćĥĩńĝ ēvēńţś ćōũĺď ƀē ƒōũńď. Embedded outpost is not configured correctly. + Ēmƀēďďēď ōũţƥōśţ ĩś ńōţ ćōńƒĩĝũŕēď ćōŕŕēćţĺŷ. Check outposts. + Ćĥēćķ ōũţƥōśţś. HTTPS is not detected correctly + ĤŢŢƤŚ ĩś ńōţ ďēţēćţēď ćōŕŕēćţĺŷ Server and client are further than 5 seconds apart. + Śēŕvēŕ àńď ćĺĩēńţ àŕē ƒũŕţĥēŕ ţĥàń 5 śēćōńďś àƥàŕţ. OK + ŌĶ Everything is ok. + Ēvēŕŷţĥĩńĝ ĩś ōķ. System status + Śŷśţēm śţàţũś Based on + ßàśēď ōń is available! + ĩś àvàĩĺàƀĺē! Up-to-date! + Ũƥ-ţō-ďàţē! Version + Vēŕśĩōń Workers + Ŵōŕķēŕś No workers connected. Background tasks will not run. + Ńō ŵōŕķēŕś ćōńńēćţēď. ßàćķĝŕōũńď ţàśķś ŵĩĺĺ ńōţ ŕũń. hour(s) ago + ĥōũŕ(ś) àĝō day(s) ago + ďàŷ(ś) àĝō Authorizations + Àũţĥōŕĩźàţĩōńś Failed Logins + Ƒàĩĺēď Ĺōĝĩńś Successful Logins + Śũććēśśƒũĺ Ĺōĝĩńś : + : Cancel + Ćàńćēĺ LDAP Source + ĹĎÀƤ Śōũŕćē SCIM Provider + ŚĆĨM Ƥŕōvĩďēŕ Healthy + Ĥēàĺţĥŷ Healthy outposts + Ĥēàĺţĥŷ ōũţƥōśţś Admin + Àďmĩń Not found + Ńōţ ƒōũńď The URL "" was not found. + Ţĥē ŨŔĹ "" ŵàś ńōţ ƒōũńď. Return home + Ŕēţũŕń ĥōmē General system status + Ĝēńēŕàĺ śŷśţēm śţàţũś Welcome, . + Ŵēĺćōmē, . Quick actions + Ǫũĩćķ àćţĩōńś Create a new application + Ćŕēàţē à ńēŵ àƥƥĺĩćàţĩōń Check the logs + Ćĥēćķ ţĥē ĺōĝś Explore integrations + Ēxƥĺōŕē ĩńţēĝŕàţĩōńś Manage users + Màńàĝē ũśēŕś Check release notes + Ćĥēćķ ŕēĺēàśē ńōţēś Outpost status + Ōũţƥōśţ śţàţũś Sync status + Śŷńć śţàţũś Logins and authorizations over the last week (per 8 hours) + Ĺōĝĩńś àńď àũţĥōŕĩźàţĩōńś ōvēŕ ţĥē ĺàśţ ŵēēķ (ƥēŕ 8 ĥōũŕś) Apps with most usage + Àƥƥś ŵĩţĥ mōśţ ũśàĝē days ago + ďàŷś àĝō Objects created + ŌƀĴēćţś ćŕēàţēď User statistics + Ũśēŕ śţàţĩśţĩćś Users created per day in the last month + Ũśēŕś ćŕēàţēď ƥēŕ ďàŷ ĩń ţĥē ĺàśţ mōńţĥ Logins per day in the last month + Ĺōĝĩńś ƥēŕ ďàŷ ĩń ţĥē ĺàśţ mōńţĥ Failed Logins per day in the last month + Ƒàĩĺēď Ĺōĝĩńś ƥēŕ ďàŷ ĩń ţĥē ĺàśţ mōńţĥ Clear search + Ćĺēàŕ śēàŕćĥ System Tasks + Śŷśţēm Ţàśķś Long-running operations which authentik executes in the background. + Ĺōńĝ-ŕũńńĩńĝ ōƥēŕàţĩōńś ŵĥĩćĥ àũţĥēńţĩķ ēxēćũţēś ĩń ţĥē ƀàćķĝŕōũńď. Identifier + Ĩďēńţĩƒĩēŕ Description + Ďēśćŕĩƥţĩōń Last run + Ĺàśţ ŕũń Status + Śţàţũś Actions + Àćţĩōńś Successful + Śũććēśśƒũĺ Error + Ēŕŕōŕ Unknown + Ũńķńōŵń Duration + Ďũŕàţĩōń seconds + śēćōńďś Authentication + Àũţĥēńţĩćàţĩōń Authorization + Àũţĥōŕĩźàţĩōń Enrollment + Ēńŕōĺĺmēńţ Invalidation + Ĩńvàĺĩďàţĩōń Recovery + Ŕēćōvēŕŷ Stage Configuration + Śţàĝē Ćōńƒĩĝũŕàţĩōń Unenrollment + Ũńēńŕōĺĺmēńţ Unknown designation + Ũńķńōŵń ďēśĩĝńàţĩōń Stacked + Śţàćķēď Content left + Ćōńţēńţ ĺēƒţ Content right + Ćōńţēńţ ŕĩĝĥţ Sidebar left + Śĩďēƀàŕ ĺēƒţ Sidebar right + Śĩďēƀàŕ ŕĩĝĥţ Unknown layout + Ũńķńōŵń ĺàŷōũţ Successfully updated provider. + Śũććēśśƒũĺĺŷ ũƥďàţēď ƥŕōvĩďēŕ. Successfully created provider. + Śũććēśśƒũĺĺŷ ćŕēàţēď ƥŕōvĩďēŕ. Bind flow + ßĩńď ƒĺōŵ Flow used for users to authenticate. + Ƒĺōŵ ũśēď ƒōŕ ũśēŕś ţō àũţĥēńţĩćàţē. Search group + Śēàŕćĥ ĝŕōũƥ Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed. + Ũśēŕś ĩń ţĥē śēĺēćţēď ĝŕōũƥ ćàń ďō śēàŕćĥ ǫũēŕĩēś. Ĩƒ ńō ĝŕōũƥ ĩś śēĺēćţēď, ńō ĹĎÀƤ Śēàŕćĥēś àŕē àĺĺōŵēď. Bind mode + ßĩńď mōďē Cached binding + Ćàćĥēď ƀĩńďĩńĝ Flow is executed and session is cached in memory. Flow is executed when session expires + Ƒĺōŵ ĩś ēxēćũţēď àńď śēśśĩōń ĩś ćàćĥēď ĩń mēmōŕŷ. Ƒĺōŵ ĩś ēxēćũţēď ŵĥēń śēśśĩōń ēxƥĩŕēś Direct binding + Ďĩŕēćţ ƀĩńďĩńĝ Always execute the configured bind flow to authenticate the user + Àĺŵàŷś ēxēćũţē ţĥē ćōńƒĩĝũŕēď ƀĩńď ƒĺōŵ ţō àũţĥēńţĩćàţē ţĥē ũśēŕ Configure how the outpost authenticates requests. + Ćōńƒĩĝũŕē ĥōŵ ţĥē ōũţƥōśţ àũţĥēńţĩćàţēś ŕēǫũēśţś. Search mode + Śēàŕćĥ mōďē Cached querying + Ćàćĥēď ǫũēŕŷĩńĝ The outpost holds all users and groups in-memory and will refresh every 5 Minutes + Ţĥē ōũţƥōśţ ĥōĺďś àĺĺ ũśēŕś àńď ĝŕōũƥś ĩń-mēmōŕŷ àńď ŵĩĺĺ ŕēƒŕēśĥ ēvēŕŷ 5 Mĩńũţēś Direct querying + Ďĩŕēćţ ǫũēŕŷĩńĝ Always returns the latest data, but slower than cached querying + Àĺŵàŷś ŕēţũŕńś ţĥē ĺàţēśţ ďàţà, ƀũţ śĺōŵēŕ ţĥàń ćàćĥēď ǫũēŕŷĩńĝ Configure how the outpost queries the core authentik server's users. + Ćōńƒĩĝũŕē ĥōŵ ţĥē ōũţƥōśţ ǫũēŕĩēś ţĥē ćōŕē àũţĥēńţĩķ śēŕvēŕ'ś ũśēŕś. Protocol settings + Ƥŕōţōćōĺ śēţţĩńĝś Base DN + ßàśē ĎŃ LDAP DN under which bind requests and search requests can be made. + ĹĎÀƤ ĎŃ ũńďēŕ ŵĥĩćĥ ƀĩńď ŕēǫũēśţś àńď śēàŕćĥ ŕēǫũēśţś ćàń ƀē màďē. Certificate + Ćēŕţĩƒĩćàţē UID start number + ŨĨĎ śţàŕţ ńũmƀēŕ The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber + Ţĥē śţàŕţ ƒōŕ ũĩďŃũmƀēŕś, ţĥĩś ńũmƀēŕ ĩś àďďēď ţō ţĥē ũśēŕ.Ƥķ ţō màķē śũŕē ţĥàţ ţĥē ńũmƀēŕś àŕēń'ţ ţōō ĺōŵ ƒōŕ ƤŌŚĨX ũśēŕś. Ďēƒàũĺţ ĩś 2000 ţō ēńśũŕē ţĥàţ ŵē ďōń'ţ ćōĺĺĩďē ŵĩţĥ ĺōćàĺ ũśēŕś ũĩďŃũmƀēŕ GID start number + ĜĨĎ śţàŕţ ńũmƀēŕ The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber + Ţĥē śţàŕţ ƒōŕ ĝĩďŃũmƀēŕś, ţĥĩś ńũmƀēŕ ĩś àďďēď ţō à ńũmƀēŕ ĝēńēŕàţēď ƒŕōm ţĥē ĝŕōũƥ.Ƥķ ţō màķē śũŕē ţĥàţ ţĥē ńũmƀēŕś àŕēń'ţ ţōō ĺōŵ ƒōŕ ƤŌŚĨX ĝŕōũƥś. Ďēƒàũĺţ ĩś 4000 ţō ēńśũŕē ţĥàţ ŵē ďōń'ţ ćōĺĺĩďē ŵĩţĥ ĺōćàĺ ĝŕōũƥś ōŕ ũśēŕś ƥŕĩmàŕŷ ĝŕōũƥś ĝĩďŃũmƀēŕ (Format: hours=-1;minutes=-2;seconds=-3). + (Ƒōŕmàţ: ĥōũŕś=-1;mĩńũţēś=-2;śēćōńďś=-3). (Format: hours=1;minutes=2;seconds=3). + (Ƒōŕmàţ: ĥōũŕś=1;mĩńũţēś=2;śēćōńďś=3). The following keywords are supported: + Ţĥē ƒōĺĺōŵĩńĝ ķēŷŵōŕďś àŕē śũƥƥōŕţēď: Authentication flow + Àũţĥēńţĩćàţĩōń ƒĺōŵ Flow used when a user access this provider and is not authenticated. + Ƒĺōŵ ũśēď ŵĥēń à ũśēŕ àććēśś ţĥĩś ƥŕōvĩďēŕ àńď ĩś ńōţ àũţĥēńţĩćàţēď. Authorization flow + Àũţĥōŕĩźàţĩōń ƒĺōŵ Flow used when authorizing this provider. + Ƒĺōŵ ũśēď ŵĥēń àũţĥōŕĩźĩńĝ ţĥĩś ƥŕōvĩďēŕ. Client type + Ćĺĩēńţ ţŷƥē Confidential + Ćōńƒĩďēńţĩàĺ Confidential clients are capable of maintaining the confidentiality of their credentials such as client secrets + Ćōńƒĩďēńţĩàĺ ćĺĩēńţś àŕē ćàƥàƀĺē ōƒ màĩńţàĩńĩńĝ ţĥē ćōńƒĩďēńţĩàĺĩţŷ ōƒ ţĥēĩŕ ćŕēďēńţĩàĺś śũćĥ àś ćĺĩēńţ śēćŕēţś Public + Ƥũƀĺĩć Public clients are incapable of maintaining the confidentiality and should use methods like PKCE. + Ƥũƀĺĩć ćĺĩēńţś àŕē ĩńćàƥàƀĺē ōƒ màĩńţàĩńĩńĝ ţĥē ćōńƒĩďēńţĩàĺĩţŷ àńď śĥōũĺď ũśē mēţĥōďś ĺĩķē ƤĶĆĒ. Client ID + Ćĺĩēńţ ĨĎ Client Secret + Ćĺĩēńţ Śēćŕēţ Redirect URIs/Origins (RegEx) + Ŕēďĩŕēćţ ŨŔĨś/Ōŕĩĝĩńś (ŔēĝĒx) Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows. + Vàĺĩď ŕēďĩŕēćţ ŨŔĹś àƒţēŕ à śũććēśśƒũĺ àũţĥōŕĩźàţĩōń ƒĺōŵ. Àĺśō śƥēćĩƒŷ àńŷ ōŕĩĝĩńś ĥēŕē ƒōŕ Ĩmƥĺĩćĩţ ƒĺōŵś. If no explicit redirect URIs are specified, the first successfully used redirect URI will be saved. + Ĩƒ ńō ēxƥĺĩćĩţ ŕēďĩŕēćţ ŨŔĨś àŕē śƥēćĩƒĩēď, ţĥē ƒĩŕśţ śũććēśśƒũĺĺŷ ũśēď ŕēďĩŕēćţ ŨŔĨ ŵĩĺĺ ƀē śàvēď. To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. + Ţō àĺĺōŵ àńŷ ŕēďĩŕēćţ ŨŔĨ, śēţ ţĥĩś vàĺũē ţō ".*". ßē àŵàŕē ōƒ ţĥē ƥōśśĩƀĺē śēćũŕĩţŷ ĩmƥĺĩćàţĩōńś ţĥĩś ćàń ĥàvē. Signing Key + Śĩĝńĩńĝ Ķēŷ Key used to sign the tokens. + Ķēŷ ũśēď ţō śĩĝń ţĥē ţōķēńś. Advanced protocol settings + Àďvàńćēď ƥŕōţōćōĺ śēţţĩńĝś Access code validity + Àććēśś ćōďē vàĺĩďĩţŷ Configure how long access codes are valid for. + Ćōńƒĩĝũŕē ĥōŵ ĺōńĝ àććēśś ćōďēś àŕē vàĺĩď ƒōŕ. Access Token validity + Àććēśś Ţōķēń vàĺĩďĩţŷ Configure how long access tokens are valid for. + Ćōńƒĩĝũŕē ĥōŵ ĺōńĝ àććēśś ţōķēńś àŕē vàĺĩď ƒōŕ. Refresh Token validity + Ŕēƒŕēśĥ Ţōķēń vàĺĩďĩţŷ Configure how long refresh tokens are valid for. + Ćōńƒĩĝũŕē ĥōŵ ĺōńĝ ŕēƒŕēśĥ ţōķēńś àŕē vàĺĩď ƒōŕ. Scopes + Śćōƥēś Select which scopes can be used by the client. The client still has to specify the scope to access the data. + Śēĺēćţ ŵĥĩćĥ śćōƥēś ćàń ƀē ũśēď ƀŷ ţĥē ćĺĩēńţ. Ţĥē ćĺĩēńţ śţĩĺĺ ĥàś ţō śƥēćĩƒŷ ţĥē śćōƥē ţō àććēśś ţĥē ďàţà. Hold control/command to select multiple items. + Ĥōĺď ćōńţŕōĺ/ćōmmàńď ţō śēĺēćţ mũĺţĩƥĺē ĩţēmś. Subject mode + ŚũƀĴēćţ mōďē Based on the User's hashed ID + ßàśēď ōń ţĥē Ũśēŕ'ś ĥàśĥēď ĨĎ Based on the User's ID + ßàśēď ōń ţĥē Ũśēŕ'ś ĨĎ Based on the User's UUID + ßàśēď ōń ţĥē Ũśēŕ'ś ŨŨĨĎ Based on the User's username + ßàśēď ōń ţĥē Ũśēŕ'ś ũśēŕńàmē Based on the User's Email + ßàśēď ōń ţĥē Ũśēŕ'ś Ēmàĩĺ This is recommended over the UPN mode. + Ţĥĩś ĩś ŕēćōmmēńďēď ōvēŕ ţĥē ŨƤŃ mōďē. Based on the User's UPN + ßàśēď ōń ţĥē Ũśēŕ'ś ŨƤŃ Requires the user to have a 'upn' attribute set, and falls back to hashed user ID. Use this mode only if you have different UPN and Mail domains. + Ŕēǫũĩŕēś ţĥē ũśēŕ ţō ĥàvē à 'ũƥń' àţţŕĩƀũţē śēţ, àńď ƒàĺĺś ƀàćķ ţō ĥàśĥēď ũśēŕ ĨĎ. Ũśē ţĥĩś mōďē ōńĺŷ ĩƒ ŷōũ ĥàvē ďĩƒƒēŕēńţ ŨƤŃ àńď Màĩĺ ďōmàĩńś. Configure what data should be used as unique User Identifier. For most cases, the default should be fine. + Ćōńƒĩĝũŕē ŵĥàţ ďàţà śĥōũĺď ƀē ũśēď àś ũńĩǫũē Ũśēŕ Ĩďēńţĩƒĩēŕ. Ƒōŕ mōśţ ćàśēś, ţĥē ďēƒàũĺţ śĥōũĺď ƀē ƒĩńē. Include claims in id_token + Ĩńćĺũďē ćĺàĩmś ĩń ĩď_ţōķēń Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint. + Ĩńćĺũďē Ũśēŕ ćĺàĩmś ƒŕōm śćōƥēś ĩń ţĥē ĩď_ţōķēń, ƒōŕ àƥƥĺĩćàţĩōńś ţĥàţ ďōń'ţ àććēśś ţĥē ũśēŕĩńƒō ēńďƥōĩńţ. Issuer mode + Ĩśśũēŕ mōďē Each provider has a different issuer, based on the application slug + Ēàćĥ ƥŕōvĩďēŕ ĥàś à ďĩƒƒēŕēńţ ĩśśũēŕ, ƀàśēď ōń ţĥē àƥƥĺĩćàţĩōń śĺũĝ Same identifier is used for all providers + Śàmē ĩďēńţĩƒĩēŕ ĩś ũśēď ƒōŕ àĺĺ ƥŕōvĩďēŕś Configure how the issuer field of the ID Token should be filled. + Ćōńƒĩĝũŕē ĥōŵ ţĥē ĩśśũēŕ ƒĩēĺď ōƒ ţĥē ĨĎ Ţōķēń śĥōũĺď ƀē ƒĩĺĺēď. Machine-to-Machine authentication settings + Màćĥĩńē-ţō-Màćĥĩńē àũţĥēńţĩćàţĩōń śēţţĩńĝś Trusted OIDC Sources + Ţŕũśţēď ŌĨĎĆ Śōũŕćēś JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider. + ĵŴŢś śĩĝńēď ƀŷ ćēŕţĩƒĩćàţēś ćōńƒĩĝũŕēď ĩń ţĥē śēĺēćţēď śōũŕćēś ćàń ƀē ũśēď ţō àũţĥēńţĩćàţē ţō ţĥĩś ƥŕōvĩďēŕ. HTTP-Basic Username Key + ĤŢŢƤ-ßàśĩć Ũśēŕńàmē Ķēŷ User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used. + Ũśēŕ/Ĝŕōũƥ Àţţŕĩƀũţē ũśēď ƒōŕ ţĥē ũśēŕ ƥàŕţ ōƒ ţĥē ĤŢŢƤ-ßàśĩć Ĥēàďēŕ. Ĩƒ ńōţ śēţ, ţĥē ũśēŕ'ś Ēmàĩĺ àďďŕēśś ĩś ũśēď. HTTP-Basic Password Key + ĤŢŢƤ-ßàśĩć Ƥàśśŵōŕď Ķēŷ User/Group Attribute used for the password part of the HTTP-Basic Header. + Ũśēŕ/Ĝŕōũƥ Àţţŕĩƀũţē ũśēď ƒōŕ ţĥē ƥàśśŵōŕď ƥàŕţ ōƒ ţĥē ĤŢŢƤ-ßàśĩć Ĥēàďēŕ. Proxy + Ƥŕōxŷ Forward auth (single application) + Ƒōŕŵàŕď àũţĥ (śĩńĝĺē àƥƥĺĩćàţĩōń) Forward auth (domain level) + Ƒōŕŵàŕď àũţĥ (ďōmàĩń ĺēvēĺ) This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well. + Ţĥĩś ƥŕōvĩďēŕ ŵĩĺĺ ƀēĥàvē ĺĩķē à ţŕàńśƥàŕēńţ ŕēvēŕśē-ƥŕōxŷ, ēxćēƥţ ŕēǫũēśţś mũśţ ƀē àũţĥēńţĩćàţēď. Ĩƒ ŷōũŕ ũƥśţŕēàm àƥƥĺĩćàţĩōń ũśēś ĤŢŢƤŚ, màķē śũŕē ţō ćōńńēćţ ţō ţĥē ōũţƥōśţ ũśĩńĝ ĤŢŢƤŚ àś ŵēĺĺ. External host + Ēxţēŕńàĺ ĥōśţ The external URL you'll access the application at. Include any non-standard port. + Ţĥē ēxţēŕńàĺ ŨŔĹ ŷōũ'ĺĺ àććēśś ţĥē àƥƥĺĩćàţĩōń àţ. Ĩńćĺũďē àńŷ ńōń-śţàńďàŕď ƥōŕţ. Internal host + Ĩńţēŕńàĺ ĥōśţ Upstream host that the requests are forwarded to. + Ũƥśţŕēàm ĥōśţ ţĥàţ ţĥē ŕēǫũēśţś àŕē ƒōŕŵàŕďēď ţō. Internal host SSL Validation + Ĩńţēŕńàĺ ĥōśţ ŚŚĹ Vàĺĩďàţĩōń Validate SSL Certificates of upstream servers. + Vàĺĩďàţē ŚŚĹ Ćēŕţĩƒĩćàţēś ōƒ ũƥśţŕēàm śēŕvēŕś. Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. + Ũśē ţĥĩś ƥŕōvĩďēŕ ŵĩţĥ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩķ'ś ƒōŕŵàŕďÀũţĥ. Ōńĺŷ à śĩńĝĺē ƥŕōvĩďēŕ ĩś ŕēǫũĩŕēď ƥēŕ ŕōōţ ďōmàĩń. Ŷōũ ćàń'ţ ďō ƥēŕ-àƥƥĺĩćàţĩōń àũţĥōŕĩźàţĩōń, ƀũţ ŷōũ ďōń'ţ ĥàvē ţō ćŕēàţē à ƥŕōvĩďēŕ ƒōŕ ēàćĥ àƥƥĺĩćàţĩōń. An example setup can look like this: + Àń ēxàmƥĺē śēţũƥ ćàń ĺōōķ ĺĩķē ţĥĩś: authentik running on auth.example.com + àũţĥēńţĩķ ŕũńńĩńĝ ōń àũţĥ.ēxàmƥĺē.ćōm app1 running on app1.example.com + àƥƥ1 ŕũńńĩńĝ ōń àƥƥ1.ēxàmƥĺē.ćōm In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com. + Ĩń ţĥĩś ćàśē, ŷōũ'ď śēţ ţĥē Àũţĥēńţĩćàţĩōń ŨŔĹ ţō àũţĥ.ēxàmƥĺē.ćōm àńď Ćōōķĩē ďōmàĩń ţō ēxàmƥĺē.ćōm. Authentication URL + Àũţĥēńţĩćàţĩōń ŨŔĹ The external URL you'll authenticate at. The authentik core server should be reachable under this URL. + Ţĥē ēxţēŕńàĺ ŨŔĹ ŷōũ'ĺĺ àũţĥēńţĩćàţē àţ. Ţĥē àũţĥēńţĩķ ćōŕē śēŕvēŕ śĥōũĺď ƀē ŕēàćĥàƀĺē ũńďēŕ ţĥĩś ŨŔĹ. Cookie domain + Ćōōķĩē ďōmàĩń 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'. + Śēţ ţĥĩś ţō ţĥē ďōmàĩń ŷōũ ŵĩśĥ ţĥē àũţĥēńţĩćàţĩōń ţō ƀē vàĺĩď ƒōŕ. Mũśţ ƀē à ƥàŕēńţ ďōmàĩń ōƒ ţĥē ŨŔĹ àƀōvē. Ĩƒ ŷōũ'ŕē ŕũńńĩńĝ àƥƥĺĩćàţĩōńś àś àƥƥ1.ďōmàĩń.ţĺď, àƥƥ2.ďōmàĩń.ţĺď, śēţ ţĥĩś ţō 'ďōmàĩń.ţĺď'. Unknown proxy mode + Ũńķńōŵń ƥŕōxŷ mōďē Token validity + Ţōķēń vàĺĩďĩţŷ Configure how long tokens are valid for. + Ćōńƒĩĝũŕē ĥōŵ ĺōńĝ ţōķēńś àŕē vàĺĩď ƒōŕ. Additional scopes + Àďďĩţĩōńàĺ śćōƥēś Additional scope mappings, which are passed to the proxy. + Àďďĩţĩōńàĺ śćōƥē màƥƥĩńĝś, ŵĥĩćĥ àŕē ƥàśśēď ţō ţĥē ƥŕōxŷ. Unauthenticated URLs + Ũńàũţĥēńţĩćàţēď ŨŔĹś Unauthenticated Paths + Ũńàũţĥēńţĩćàţēď Ƥàţĥś Regular expressions for which authentication is not required. Each new line is interpreted as a new expression. + Ŕēĝũĺàŕ ēxƥŕēśśĩōńś ƒōŕ ŵĥĩćĥ àũţĥēńţĩćàţĩōń ĩś ńōţ ŕēǫũĩŕēď. Ēàćĥ ńēŵ ĺĩńē ĩś ĩńţēŕƥŕēţēď àś à ńēŵ ēxƥŕēśśĩōń. 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. + Ŵĥēń ũśĩńĝ ƥŕōxŷ ōŕ ƒōŕŵàŕď àũţĥ (śĩńĝĺē àƥƥĺĩćàţĩōń) mōďē, ţĥē ŕēǫũēśţēď ŨŔĹ Ƥàţĥ ĩś ćĥēćķēď àĝàĩńśţ ţĥē ŕēĝũĺàŕ ēxƥŕēśśĩōńś. Ŵĥēń ũśĩńĝ ƒōŕŵàŕď àũţĥ (ďōmàĩń mōďē), ţĥē ƒũĺĺ ŕēǫũēśţēď ŨŔĹ ĩńćĺũďĩńĝ śćĥēmē àńď ĥōśţ ĩś màţćĥēď àĝàĩńśţ ţĥē ŕēĝũĺàŕ ēxƥŕēśśĩōńś. Authentication settings + Àũţĥēńţĩćàţĩōń śēţţĩńĝś Intercept header authentication + Ĩńţēŕćēƥţ ĥēàďēŕ àũţĥēńţĩćàţĩōń When enabled, authentik will intercept the Authorization header to authenticate the request. + Ŵĥēń ēńàƀĺēď, àũţĥēńţĩķ ŵĩĺĺ ĩńţēŕćēƥţ ţĥē Àũţĥōŕĩźàţĩōń ĥēàďēŕ ţō àũţĥēńţĩćàţē ţĥē ŕēǫũēśţ. Send HTTP-Basic Authentication + Śēńď ĤŢŢƤ-ßàśĩć Àũţĥēńţĩćàţĩōń Send a custom HTTP-Basic Authentication header based on values from authentik. + Śēńď à ćũśţōm ĤŢŢƤ-ßàśĩć Àũţĥēńţĩćàţĩōń ĥēàďēŕ ƀàśēď ōń vàĺũēś ƒŕōm àũţĥēńţĩķ. ACS URL + ÀĆŚ ŨŔĹ Issuer + Ĩśśũēŕ Also known as EntityID. + Àĺśō ķńōŵń àś ĒńţĩţŷĨĎ. Service Provider Binding + Śēŕvĩćē Ƥŕōvĩďēŕ ßĩńďĩńĝ Redirect + Ŕēďĩŕēćţ Post + Ƥōśţ Determines how authentik sends the response back to the Service Provider. + Ďēţēŕmĩńēś ĥōŵ àũţĥēńţĩķ śēńďś ţĥē ŕēśƥōńśē ƀàćķ ţō ţĥē Śēŕvĩćē Ƥŕōvĩďēŕ. Audience + Àũďĩēńćē Signing Certificate + Śĩĝńĩńĝ Ćēŕţĩƒĩćàţē Certificate used to sign outgoing Responses going to the Service Provider. + Ćēŕţĩƒĩćàţē ũśēď ţō śĩĝń ōũţĝōĩńĝ Ŕēśƥōńśēś ĝōĩńĝ ţō ţĥē Śēŕvĩćē Ƥŕōvĩďēŕ. Verification Certificate + Vēŕĩƒĩćàţĩōń Ćēŕţĩƒĩćàţē When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default. + Ŵĥēń śēĺēćţēď, ĩńćōmĩńĝ àśśēŕţĩōń'ś Śĩĝńàţũŕēś ŵĩĺĺ ƀē vàĺĩďàţēď àĝàĩńśţ ţĥĩś ćēŕţĩƒĩćàţē. Ţō àĺĺōŵ ũńśĩĝńēď Ŕēǫũēśţś, ĺēàvē ōń ďēƒàũĺţ. Property mappings + Ƥŕōƥēŕţŷ màƥƥĩńĝś NameID Property Mapping + ŃàmēĨĎ Ƥŕōƥēŕţŷ Màƥƥĩńĝ Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected. + Ćōńƒĩĝũŕē ĥōŵ ţĥē ŃàmēĨĎ vàĺũē ŵĩĺĺ ƀē ćŕēàţēď. Ŵĥēń ĺēƒţ ēmƥţŷ, ţĥē ŃàmēĨĎƤōĺĩćŷ ōƒ ţĥē ĩńćōmĩńĝ ŕēǫũēśţ ŵĩĺĺ ƀē ŕēśƥēćţēď. Assertion valid not before + Àśśēŕţĩōń vàĺĩď ńōţ ƀēƒōŕē Configure the maximum allowed time drift for an assertion. + Ćōńƒĩĝũŕē ţĥē màxĩmũm àĺĺōŵēď ţĩmē ďŕĩƒţ ƒōŕ àń àśśēŕţĩōń. Assertion valid not on or after + Àśśēŕţĩōń vàĺĩď ńōţ ōń ōŕ àƒţēŕ Assertion not valid on or after current time + this value. + Àśśēŕţĩōń ńōţ vàĺĩď ōń ōŕ àƒţēŕ ćũŕŕēńţ ţĩmē + ţĥĩś vàĺũē. Session valid not on or after + Śēśśĩōń vàĺĩď ńōţ ōń ōŕ àƒţēŕ Session not valid on or after current time + this value. + Śēśśĩōń ńōţ vàĺĩď ōń ōŕ àƒţēŕ ćũŕŕēńţ ţĩmē + ţĥĩś vàĺũē. Digest algorithm + Ďĩĝēśţ àĺĝōŕĩţĥm Signature algorithm + Śĩĝńàţũŕē àĺĝōŕĩţĥm Successfully imported provider. + Śũććēśśƒũĺĺŷ ĩmƥōŕţēď ƥŕōvĩďēŕ. Metadata + Mēţàďàţà Apply changes + Àƥƥĺŷ ćĥàńĝēś Close + Ćĺōśē Finish + Ƒĩńĩśĥ Back + ßàćķ No form found + Ńō ƒōŕm ƒōũńď Form didn't return a promise for submitting + Ƒōŕm ďĩďń'ţ ŕēţũŕń à ƥŕōmĩśē ƒōŕ śũƀmĩţţĩńĝ Select type + Śēĺēćţ ţŷƥē Try the new application wizard + Ţŕŷ ţĥē ńēŵ àƥƥĺĩćàţĩōń ŵĩźàŕď The new application wizard greatly simplifies the steps required to create applications and providers. + Ţĥē ńēŵ àƥƥĺĩćàţĩōń ŵĩźàŕď ĝŕēàţĺŷ śĩmƥĺĩƒĩēś ţĥē śţēƥś ŕēǫũĩŕēď ţō ćŕēàţē àƥƥĺĩćàţĩōńś àńď ƥŕōvĩďēŕś. Try it now + Ţŕŷ ĩţ ńōŵ Create + Ćŕēàţē New provider + Ńēŵ ƥŕōvĩďēŕ Create a new provider. + Ćŕēàţē à ńēŵ ƥŕōvĩďēŕ. Create + Ćŕēàţē Shared secret + Śĥàŕēď śēćŕēţ Client Networks + Ćĺĩēńţ Ńēţŵōŕķś 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 will be dropped. + Ĺĩśţ ōƒ ĆĨĎŔś (ćōmmà-śēƥēŕàţēď) ţĥàţ ćĺĩēńţś ćàń ćōńńēćţ ƒŕōm. À mōŕē śƥēćĩƒĩć + ĆĨĎŔ ŵĩĺĺ màţćĥ ƀēƒōŕē à ĺōōśēŕ ōńē. Ćĺĩēńţś ćōńńēćţĩńĝ ƒŕōm à ńōń-śƥēćĩƒĩēď ĆĨĎŔ + ŵĩĺĺ ƀē ďŕōƥƥēď. URL + ŨŔĹ SCIM base url, usually ends in /v2. + ŚĆĨM ƀàśē ũŕĺ, ũśũàĺĺŷ ēńďś ĩń /v2. Token + Ţōķēń Token to authenticate with. Currently only bearer authentication is supported. + Ţōķēń ţō àũţĥēńţĩćàţē ŵĩţĥ. Ćũŕŕēńţĺŷ ōńĺŷ ƀēàŕēŕ àũţĥēńţĩćàţĩōń ĩś śũƥƥōŕţēď. User filtering + Ũśēŕ ƒĩĺţēŕĩńĝ Exclude service accounts + Ēxćĺũďē śēŕvĩćē àććōũńţś Group + Ĝŕōũƥ Only sync users within the selected group. + Ōńĺŷ śŷńć ũśēŕś ŵĩţĥĩń ţĥē śēĺēćţēď ĝŕōũƥ. Attribute mapping + Àţţŕĩƀũţē màƥƥĩńĝ User Property Mappings + Ũśēŕ Ƥŕōƥēŕţŷ Màƥƥĩńĝś Property mappings used to user mapping. + Ƥŕōƥēŕţŷ màƥƥĩńĝś ũśēď ţō ũśēŕ màƥƥĩńĝ. Group Property Mappings + Ĝŕōũƥ Ƥŕōƥēŕţŷ Màƥƥĩńĝś Property mappings used to group creation. + Ƥŕōƥēŕţŷ màƥƥĩńĝś ũśēď ţō ĝŕōũƥ ćŕēàţĩōń. Not used by any other object. + Ńōţ ũśēď ƀŷ àńŷ ōţĥēŕ ōƀĴēćţ. object will be DELETED + ōƀĴēćţ ŵĩĺĺ ƀē ĎĒĹĒŢĒĎ connection will be deleted + ćōńńēćţĩōń ŵĩĺĺ ƀē ďēĺēţēď reference will be reset to default value + ŕēƒēŕēńćē ŵĩĺĺ ƀē ŕēśēţ ţō ďēƒàũĺţ vàĺũē reference will be set to an empty value + ŕēƒēŕēńćē ŵĩĺĺ ƀē śēţ ţō àń ēmƥţŷ vàĺũē () + () ID + ĨĎ Successfully deleted + Śũććēśśƒũĺĺŷ ďēĺēţēď Failed to delete : + Ƒàĩĺēď ţō ďēĺēţē : Delete + Ďēĺēţē Are you sure you want to delete ? + Àŕē ŷōũ śũŕē ŷōũ ŵàńţ ţō ďēĺēţē ? Delete + Ďēĺēţē Providers + Ƥŕōvĩďēŕś Provide support for protocols like SAML and OAuth to assigned applications. + Ƥŕōvĩďē śũƥƥōŕţ ƒōŕ ƥŕōţōćōĺś ĺĩķē ŚÀMĹ àńď ŌÀũţĥ ţō àśśĩĝńēď àƥƥĺĩćàţĩōńś. Type + Ţŷƥē Provider(s) + Ƥŕōvĩďēŕ(ś) Assigned to application + Àśśĩĝńēď ţō àƥƥĺĩćàţĩōń Assigned to application (backchannel) + Àśśĩĝńēď ţō àƥƥĺĩćàţĩōń (ƀàćķćĥàńńēĺ) Warning: Provider not assigned to any application. + Ŵàŕńĩńĝ: Ƥŕōvĩďēŕ ńōţ àśśĩĝńēď ţō àńŷ àƥƥĺĩćàţĩōń. Update + Ũƥďàţē Update + Ũƥďàţē Select providers to add to application + Śēĺēćţ ƥŕōvĩďēŕś ţō àďď ţō àƥƥĺĩćàţĩōń Add + Àďď Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". + Ēĩţĥēŕ ĩńƥũţ à ƒũĺĺ ŨŔĹ, à ŕēĺàţĩvē ƥàţĥ, ōŕ ũśē 'ƒà://ƒà-ţēśţ' ţō ũśē ţĥē Ƒōńţ Àŵēśōmē ĩćōń "ƒà-ţēśţ". Path template for users created. Use placeholders like `%(slug)s` to insert the source slug. + Ƥàţĥ ţēmƥĺàţē ƒōŕ ũśēŕś ćŕēàţēď. Ũśē ƥĺàćēĥōĺďēŕś ĺĩķē `%(śĺũĝ)ś` ţō ĩńśēŕţ ţĥē śōũŕćē śĺũĝ. Successfully updated application. + Śũććēśśƒũĺĺŷ ũƥďàţēď àƥƥĺĩćàţĩōń. Successfully created application. + Śũććēśśƒũĺĺŷ ćŕēàţēď àƥƥĺĩćàţĩōń. Application's display Name. + Àƥƥĺĩćàţĩōń'ś ďĩśƥĺàŷ Ńàmē. Slug - - - - Internal application name, used in URLs. + Śĺũĝ Optionally enter a group name. Applications with identical groups are shown grouped together. + Ōƥţĩōńàĺĺŷ ēńţēŕ à ĝŕōũƥ ńàmē. Àƥƥĺĩćàţĩōńś ŵĩţĥ ĩďēńţĩćàĺ ĝŕōũƥś àŕē śĥōŵń ĝŕōũƥēď ţōĝēţĥēŕ. Provider + Ƥŕōvĩďēŕ Select a provider that this application should use. - - - - Backchannel providers + Śēĺēćţ à ƥŕōvĩďēŕ ţĥàţ ţĥĩś àƥƥĺĩćàţĩōń śĥōũĺď ũśē. Select backchannel providers which augment the functionality of the main provider. + Śēĺēćţ ƀàćķćĥàńńēĺ ƥŕōvĩďēŕś ŵĥĩćĥ àũĝmēńţ ţĥē ƒũńćţĩōńàĺĩţŷ ōƒ ţĥē màĩń ƥŕōvĩďēŕ. Policy engine mode + Ƥōĺĩćŷ ēńĝĩńē mōďē Any policy must match to grant access + Àńŷ ƥōĺĩćŷ mũśţ màţćĥ ţō ĝŕàńţ àććēśś All policies must match to grant access + Àĺĺ ƥōĺĩćĩēś mũśţ màţćĥ ţō ĝŕàńţ àććēśś UI settings + ŨĨ śēţţĩńĝś Launch URL + Ĺàũńćĥ ŨŔĹ If left empty, authentik will try to extract the launch URL based on the selected provider. + Ĩƒ ĺēƒţ ēmƥţŷ, àũţĥēńţĩķ ŵĩĺĺ ţŕŷ ţō ēxţŕàćţ ţĥē ĺàũńćĥ ŨŔĹ ƀàśēď ōń ţĥē śēĺēćţēď ƥŕōvĩďēŕ. Open in new tab + Ōƥēń ĩń ńēŵ ţàƀ If checked, the launch URL will open in a new browser tab or window from the user's application library. + Ĩƒ ćĥēćķēď, ţĥē ĺàũńćĥ ŨŔĹ ŵĩĺĺ ōƥēń ĩń à ńēŵ ƀŕōŵśēŕ ţàƀ ōŕ ŵĩńďōŵ ƒŕōm ţĥē ũśēŕ'ś àƥƥĺĩćàţĩōń ĺĩƀŕàŕŷ. Icon + Ĩćōń Currently set to: + Ćũŕŕēńţĺŷ śēţ ţō: Clear icon + Ćĺēàŕ ĩćōń Publisher + Ƥũƀĺĩśĥēŕ Create Application + Ćŕēàţē Àƥƥĺĩćàţĩōń Overview + Ōvēŕvĩēŵ Changelog + Ćĥàńĝēĺōĝ Warning: Provider is not used by any Outpost. + Ŵàŕńĩńĝ: Ƥŕōvĩďēŕ ĩś ńōţ ũśēď ƀŷ àńŷ Ōũţƥōśţ. Assigned to application + Àśśĩĝńēď ţō àƥƥĺĩćàţĩōń Update LDAP Provider + Ũƥďàţē ĹĎÀƤ Ƥŕōvĩďēŕ Edit + Ēďĩţ How to connect + Ĥōŵ ţō ćōńńēćţ Connect to the LDAP Server on port 389: + Ćōńńēćţ ţō ţĥē ĹĎÀƤ Śēŕvēŕ ōń ƥōŕţ 389: Check the IP of the Kubernetes service, or + Ćĥēćķ ţĥē ĨƤ ōƒ ţĥē Ķũƀēŕńēţēś śēŕvĩćē, ōŕ The Host IP of the docker host + Ţĥē Ĥōśţ ĨƤ ōƒ ţĥē ďōćķēŕ ĥōśţ Bind DN + ßĩńď ĎŃ Bind Password + ßĩńď Ƥàśśŵōŕď Search base + Śēàŕćĥ ƀàśē Preview + Ƥŕēvĩēŵ Warning: Provider is not used by an Application. + Ŵàŕńĩńĝ: Ƥŕōvĩďēŕ ĩś ńōţ ũśēď ƀŷ àń Àƥƥĺĩćàţĩōń. Redirect URIs + Ŕēďĩŕēćţ ŨŔĨś Update OAuth2 Provider + Ũƥďàţē ŌÀũţĥ2 Ƥŕōvĩďēŕ OpenID Configuration URL + ŌƥēńĨĎ Ćōńƒĩĝũŕàţĩōń ŨŔĹ OpenID Configuration Issuer + ŌƥēńĨĎ Ćōńƒĩĝũŕàţĩōń Ĩśśũēŕ Authorize URL + Àũţĥōŕĩźē ŨŔĹ Token URL + Ţōķēń ŨŔĹ Userinfo URL + Ũśēŕĩńƒō ŨŔĹ Logout URL + Ĺōĝōũţ ŨŔĹ JWKS URL + ĵŴĶŚ ŨŔĹ Example JWT payload (for currently authenticated user) + Ēxàmƥĺē ĵŴŢ ƥàŷĺōàď (ƒōŕ ćũŕŕēńţĺŷ àũţĥēńţĩćàţēď ũśēŕ) Forward auth (domain-level) + Ƒōŕŵàŕď àũţĥ (ďōmàĩń-ĺēvēĺ) Nginx (Ingress) + Ńĝĩńx (Ĩńĝŕēśś) Nginx (Proxy Manager) + Ńĝĩńx (Ƥŕōxŷ Màńàĝēŕ) Nginx (standalone) + Ńĝĩńx (śţàńďàĺōńē) Traefik (Ingress) + Ţŕàēƒĩķ (Ĩńĝŕēśś) Traefik (Compose) + Ţŕàēƒĩķ (Ćōmƥōśē) Traefik (Standalone) + Ţŕàēƒĩķ (Śţàńďàĺōńē) Caddy (Standalone) + Ćàďďŷ (Śţàńďàĺōńē) Internal Host + Ĩńţēŕńàĺ Ĥōśţ External Host + Ēxţēŕńàĺ Ĥōśţ Basic-Auth + ßàśĩć-Àũţĥ Yes + Ŷēś Mode + Mōďē Update Proxy Provider + Ũƥďàţē Ƥŕōxŷ Ƥŕōvĩďēŕ Protocol Settings + Ƥŕōţōćōĺ Śēţţĩńĝś Allowed Redirect URIs + Àĺĺōŵēď Ŕēďĩŕēćţ ŨŔĨś Setup + Śēţũƥ No additional setup is required. + Ńō àďďĩţĩōńàĺ śēţũƥ ĩś ŕēǫũĩŕēď. Update Radius Provider + Ũƥďàţē Ŕàďĩũś Ƥŕōvĩďēŕ Download + Ďōŵńĺōàď Copy download URL + Ćōƥŷ ďōŵńĺōàď ŨŔĹ Download signing certificate + Ďōŵńĺōàď śĩĝńĩńĝ ćēŕţĩƒĩćàţē Related objects + Ŕēĺàţēď ōƀĴēćţś Update SAML Provider + Ũƥďàţē ŚÀMĹ Ƥŕōvĩďēŕ SAML Configuration + ŚÀMĹ Ćōńƒĩĝũŕàţĩōń EntityID/Issuer + ĒńţĩţŷĨĎ/Ĩśśũēŕ SSO URL (Post) + ŚŚŌ ŨŔĹ (Ƥōśţ) SSO URL (Redirect) + ŚŚŌ ŨŔĹ (Ŕēďĩŕēćţ) SSO URL (IdP-initiated Login) + ŚŚŌ ŨŔĹ (ĨďƤ-ĩńĩţĩàţēď Ĺōĝĩń) SLO URL (Post) + ŚĹŌ ŨŔĹ (Ƥōśţ) SLO URL (Redirect) + ŚĹŌ ŨŔĹ (Ŕēďĩŕēćţ) SAML Metadata + ŚÀMĹ Mēţàďàţà Example SAML attributes + Ēxàmƥĺē ŚÀMĹ àţţŕĩƀũţēś NameID attribute - - - - SCIM provider is in preview. - + ŃàmēĨĎ àţţŕĩƀũţē Warning: Provider is not assigned to an application as backchannel provider. + Ŵàŕńĩńĝ: Ƥŕōvĩďēŕ ĩś ńōţ àśśĩĝńēď ţō àń àƥƥĺĩćàţĩōń àś ƀàćķćĥàńńēĺ ƥŕōvĩďēŕ. Update SCIM Provider + Ũƥďàţē ŚĆĨM Ƥŕōvĩďēŕ Sync not run yet. + Śŷńć ńōţ ŕũń ŷēţ. Run sync again - - - - Application details - - - - Create application - - - - Additional UI settings - - - - OAuth2/OIDC + Ŕũń śŷńć àĝàĩń Modern applications, APIs and Single-page applications. - - - - SAML - - - - XML-based SSO standard. Use this if your application only supports SAML. - - - - Legacy applications which don't natively support SSO. + Mōďēŕń àƥƥĺĩćàţĩōńś, ÀƤĨś àńď Śĩńĝĺē-ƥàĝē àƥƥĺĩćàţĩōńś. LDAP + ĹĎÀƤ Provide an LDAP interface for applications and users to authenticate against. - - - - Link - - - - Authentication method - - - - LDAP details - - - - Create service account - - - - Create provider - - - - Application Link - - - - URL which will be opened when a user clicks on the application. - - - - Method details - - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - - Web application - - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - - - - Single-page applications - - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - - Native application - - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - - - - API - - - - Authentication without user interaction, or machine-to-machine authentication. - - - - Application type - - - - Flow used when users access this application. - - - - Proxy details - - - - External domain - - - - External domain you will be accessing the domain from. - - - - Import SAML Metadata - - - - Import the metadata document of the applicaation you want to configure. - - - - Manual configuration - - - - Manually configure SAML - - - - SAML details - - - - URL that authentik will redirect back to after successful authentication. - - - - Import SAML metadata + Ƥŕōvĩďē àń ĹĎÀƤ ĩńţēŕƒàćē ƒōŕ àƥƥĺĩćàţĩōńś àńď ũśēŕś ţō àũţĥēńţĩćàţē àĝàĩńśţ. New application - - - - Create a new application. + Ńēŵ àƥƥĺĩćàţĩōń Applications - - - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. + Àƥƥĺĩćàţĩōńś Provider Type + Ƥŕōvĩďēŕ Ţŷƥē Application(s) + Àƥƥĺĩćàţĩōń(ś) Application Icon + Àƥƥĺĩćàţĩōń Ĩćōń Update Application + Ũƥďàţē Àƥƥĺĩćàţĩōń Successfully sent test-request. + Śũććēśśƒũĺĺŷ śēńţ ţēśţ-ŕēǫũēśţ. Log messages + Ĺōĝ mēśśàĝēś No log messages. + Ńō ĺōĝ mēśśàĝēś. Active + Àćţĩvē Last login + Ĺàśţ ĺōĝĩń Select users to add + Śēĺēćţ ũśēŕś ţō àďď Successfully updated group. + Śũććēśśƒũĺĺŷ ũƥďàţēď ĝŕōũƥ. Successfully created group. + Śũććēśśƒũĺĺŷ ćŕēàţēď ĝŕōũƥ. Is superuser + Ĩś śũƥēŕũśēŕ Users added to this group will be superusers. + Ũśēŕś àďďēď ţō ţĥĩś ĝŕōũƥ ŵĩĺĺ ƀē śũƥēŕũśēŕś. Parent + Ƥàŕēńţ Attributes + Àţţŕĩƀũţēś Set custom attributes using YAML or JSON. + Śēţ ćũśţōm àţţŕĩƀũţēś ũśĩńĝ ŶÀMĹ ōŕ ĵŚŌŃ. Successfully updated binding. + Śũććēśśƒũĺĺŷ ũƥďàţēď ƀĩńďĩńĝ. Successfully created binding. + Śũććēśśƒũĺĺŷ ćŕēàţēď ƀĩńďĩńĝ. Policy + Ƥōĺĩćŷ Group mappings can only be checked if a user is already logged in when trying to access this source. + Ĝŕōũƥ màƥƥĩńĝś ćàń ōńĺŷ ƀē ćĥēćķēď ĩƒ à ũśēŕ ĩś àĺŕēàďŷ ĺōĝĝēď ĩń ŵĥēń ţŕŷĩńĝ ţō àććēśś ţĥĩś śōũŕćē. User mappings can only be checked if a user is already logged in when trying to access this source. + Ũśēŕ màƥƥĩńĝś ćàń ōńĺŷ ƀē ćĥēćķēď ĩƒ à ũśēŕ ĩś àĺŕēàďŷ ĺōĝĝēď ĩń ŵĥēń ţŕŷĩńĝ ţō àććēśś ţĥĩś śōũŕćē. Enabled + Ēńàƀĺēď Negate result + Ńēĝàţē ŕēśũĺţ Negates the outcome of the binding. Messages are unaffected. + Ńēĝàţēś ţĥē ōũţćōmē ōƒ ţĥē ƀĩńďĩńĝ. Mēśśàĝēś àŕē ũńàƒƒēćţēď. Order + Ōŕďēŕ Timeout + Ţĩmēōũţ Successfully updated policy. + Śũććēśśƒũĺĺŷ ũƥďàţēď ƥōĺĩćŷ. Successfully created policy. + Śũććēśśƒũĺĺŷ ćŕēàţēď ƥōĺĩćŷ. A policy used for testing. Always returns the same result as specified below after waiting a random duration. + À ƥōĺĩćŷ ũśēď ƒōŕ ţēśţĩńĝ. Àĺŵàŷś ŕēţũŕńś ţĥē śàmē ŕēśũĺţ àś śƥēćĩƒĩēď ƀēĺōŵ àƒţēŕ ŵàĩţĩńĝ à ŕàńďōm ďũŕàţĩōń. Execution logging + Ēxēćũţĩōń ĺōĝĝĩńĝ When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged. + Ŵĥēń ţĥĩś ōƥţĩōń ĩś ēńàƀĺēď, àĺĺ ēxēćũţĩōńś ōƒ ţĥĩś ƥōĺĩćŷ ŵĩĺĺ ƀē ĺōĝĝēď. ßŷ ďēƒàũĺţ, ōńĺŷ ēxēćũţĩōń ēŕŕōŕś àŕē ĺōĝĝēď. Policy-specific settings + Ƥōĺĩćŷ-śƥēćĩƒĩć śēţţĩńĝś Pass policy? + Ƥàśś ƥōĺĩćŷ? Wait (min) + Ŵàĩţ (mĩń) The policy takes a random time to execute. This controls the minimum time it will take. + Ţĥē ƥōĺĩćŷ ţàķēś à ŕàńďōm ţĩmē ţō ēxēćũţē. Ţĥĩś ćōńţŕōĺś ţĥē mĩńĩmũm ţĩmē ĩţ ŵĩĺĺ ţàķē. Wait (max) + Ŵàĩţ (màx) Matches an event against a set of criteria. If any of the configured values match, the policy passes. + Màţćĥēś àń ēvēńţ àĝàĩńśţ à śēţ ōƒ ćŕĩţēŕĩà. Ĩƒ àńŷ ōƒ ţĥē ćōńƒĩĝũŕēď vàĺũēś màţćĥ, ţĥē ƥōĺĩćŷ ƥàśśēś. Match created events with this action type. When left empty, all action types will be matched. + Màţćĥ ćŕēàţēď ēvēńţś ŵĩţĥ ţĥĩś àćţĩōń ţŷƥē. Ŵĥēń ĺēƒţ ēmƥţŷ, àĺĺ àćţĩōń ţŷƥēś ŵĩĺĺ ƀē màţćĥēď. Matches Event's Client IP (strict matching, for network matching use an Expression Policy. + Màţćĥēś Ēvēńţ'ś Ćĺĩēńţ ĨƤ (śţŕĩćţ màţćĥĩńĝ, ƒōŕ ńēţŵōŕķ màţćĥĩńĝ ũśē àń Ēxƥŕēśśĩōń Ƥōĺĩćŷ. Match events created by selected application. When left empty, all applications are matched. + Màţćĥ ēvēńţś ćŕēàţēď ƀŷ śēĺēćţēď àƥƥĺĩćàţĩōń. Ŵĥēń ĺēƒţ ēmƥţŷ, àĺĺ àƥƥĺĩćàţĩōńś àŕē màţćĥēď. Checks if the request's user's password has been changed in the last x days, and denys based on settings. + Ćĥēćķś ĩƒ ţĥē ŕēǫũēśţ'ś ũśēŕ'ś ƥàśśŵōŕď ĥàś ƀēēń ćĥàńĝēď ĩń ţĥē ĺàśţ x ďàŷś, àńď ďēńŷś ƀàśēď ōń śēţţĩńĝś. Maximum age (in days) + Màxĩmũm àĝē (ĩń ďàŷś) Only fail the policy, don't invalidate user's password + Ōńĺŷ ƒàĩĺ ţĥē ƥōĺĩćŷ, ďōń'ţ ĩńvàĺĩďàţē ũśēŕ'ś ƥàśśŵōŕď Executes the python snippet to determine whether to allow or deny a request. + Ēxēćũţēś ţĥē ƥŷţĥōń śńĩƥƥēţ ţō ďēţēŕmĩńē ŵĥēţĥēŕ ţō àĺĺōŵ ōŕ ďēńŷ à ŕēǫũēśţ. Expression using Python. + Ēxƥŕēśśĩōń ũśĩńĝ Ƥŷţĥōń. See documentation for a list of all variables. + Śēē ďōćũmēńţàţĩōń ƒōŕ à ĺĩśţ ōƒ àĺĺ vàŕĩàƀĺēś. Static rules + Śţàţĩć ŕũĺēś Minimum length + Mĩńĩmũm ĺēńĝţĥ Minimum amount of Uppercase Characters + Mĩńĩmũm àmōũńţ ōƒ Ũƥƥēŕćàśē Ćĥàŕàćţēŕś Minimum amount of Lowercase Characters + Mĩńĩmũm àmōũńţ ōƒ Ĺōŵēŕćàśē Ćĥàŕàćţēŕś Minimum amount of Digits + Mĩńĩmũm àmōũńţ ōƒ Ďĩĝĩţś Minimum amount of Symbols Characters + Mĩńĩmũm àmōũńţ ōƒ Śŷmƀōĺś Ćĥàŕàćţēŕś Error message + Ēŕŕōŕ mēśśàĝē Symbol charset + Śŷmƀōĺ ćĥàŕśēţ Characters which are considered as symbols. + Ćĥàŕàćţēŕś ŵĥĩćĥ àŕē ćōńśĩďēŕēď àś śŷmƀōĺś. HaveIBeenPwned settings + ĤàvēĨßēēńƤŵńēď śēţţĩńĝś Allowed count + Àĺĺōŵēď ćōũńţ Allow up to N occurrences in the HIBP database. + Àĺĺōŵ ũƥ ţō Ń ōććũŕŕēńćēś ĩń ţĥē ĤĨßƤ ďàţàƀàśē. zxcvbn settings + źxćvƀń śēţţĩńĝś Score threshold + Śćōŕē ţĥŕēśĥōĺď If the password's score is less than or equal this value, the policy will fail. + Ĩƒ ţĥē ƥàśśŵōŕď'ś śćōŕē ĩś ĺēśś ţĥàń ōŕ ēǫũàĺ ţĥĩś vàĺũē, ţĥē ƥōĺĩćŷ ŵĩĺĺ ƒàĩĺ. 0: Too guessable: risky password. (guesses < 10^3) + 0: Ţōō ĝũēśśàƀĺē: ŕĩśķŷ ƥàśśŵōŕď. (ĝũēśśēś < 10^3) 1: Very guessable: protection from throttled online attacks. (guesses < 10^6) + 1: Vēŕŷ ĝũēśśàƀĺē: ƥŕōţēćţĩōń ƒŕōm ţĥŕōţţĺēď ōńĺĩńē àţţàćķś. (ĝũēśśēś < 10^6) 2: Somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8) + 2: Śōmēŵĥàţ ĝũēśśàƀĺē: ƥŕōţēćţĩōń ƒŕōm ũńţĥŕōţţĺēď ōńĺĩńē àţţàćķś. (ĝũēśśēś < 10^8) 3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10) + 3: Śàƒēĺŷ ũńĝũēśśàƀĺē: mōďēŕàţē ƥŕōţēćţĩōń ƒŕōm ōƒƒĺĩńē śĺōŵ-ĥàśĥ śćēńàŕĩō. (ĝũēśśēś < 10^10) 4: Very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10) + 4: Vēŕŷ ũńĝũēśśàƀĺē: śţŕōńĝ ƥŕōţēćţĩōń ƒŕōm ōƒƒĺĩńē śĺōŵ-ĥàśĥ śćēńàŕĩō. (ĝũēśśēś >= 10^10) Checks the value from the policy request against several rules, mostly used to ensure password strength. + Ćĥēćķś ţĥē vàĺũē ƒŕōm ţĥē ƥōĺĩćŷ ŕēǫũēśţ àĝàĩńśţ śēvēŕàĺ ŕũĺēś, mōśţĺŷ ũśēď ţō ēńśũŕē ƥàśśŵōŕď śţŕēńĝţĥ. Password field + Ƥàśśŵōŕď ƒĩēĺď Field key to check, field keys defined in Prompt stages are available. + Ƒĩēĺď ķēŷ ţō ćĥēćķ, ƒĩēĺď ķēŷś ďēƒĩńēď ĩń Ƥŕōmƥţ śţàĝēś àŕē àvàĩĺàƀĺē. Check static rules + Ćĥēćķ śţàţĩć ŕũĺēś Check haveibeenpwned.com + Ćĥēćķ ĥàvēĩƀēēńƥŵńēď.ćōm For more info see: + Ƒōŕ mōŕē ĩńƒō śēē: Check zxcvbn + Ćĥēćķ źxćvƀń Password strength estimator created by Dropbox, see: + Ƥàśśŵōŕď śţŕēńĝţĥ ēśţĩmàţōŕ ćŕēàţēď ƀŷ Ďŕōƥƀōx, śēē: Allows/denys requests based on the users and/or the IPs reputation. + Àĺĺōŵś/ďēńŷś ŕēǫũēśţś ƀàśēď ōń ţĥē ũśēŕś àńď/ōŕ ţĥē ĨƤś ŕēƥũţàţĩōń. Invalid login attempts will decrease the score for the client's IP, and the username they are attempting to login as, by one. + Ĩńvàĺĩď ĺōĝĩń àţţēmƥţś ŵĩĺĺ ďēćŕēàśē ţĥē śćōŕē ƒōŕ ţĥē ćĺĩēńţ'ś ĨƤ, àńď ţĥē +ũśēŕńàmē ţĥēŷ àŕē àţţēmƥţĩńĝ ţō ĺōĝĩń àś, ƀŷ ōńē. The policy passes when the reputation score is below the threshold, and doesn't pass when either or both of the selected options are equal or above the threshold. + Ţĥē ƥōĺĩćŷ ƥàśśēś ŵĥēń ţĥē ŕēƥũţàţĩōń śćōŕē ĩś ƀēĺōŵ ţĥē ţĥŕēśĥōĺď, àńď +ďōēśń'ţ ƥàśś ŵĥēń ēĩţĥēŕ ōŕ ƀōţĥ ōƒ ţĥē śēĺēćţēď ōƥţĩōńś àŕē ēǫũàĺ ōŕ àƀōvē ţĥē ţĥŕēśĥōĺď. Check IP + Ćĥēćķ ĨƤ Check Username + Ćĥēćķ Ũśēŕńàmē Threshold + Ţĥŕēśĥōĺď New policy + Ńēŵ ƥōĺĩćŷ Create a new policy. + Ćŕēàţē à ńēŵ ƥōĺĩćŷ. Create Binding + Ćŕēàţē ßĩńďĩńĝ Superuser + Śũƥēŕũśēŕ Members + Mēmƀēŕś Select groups to add user to + Śēĺēćţ ĝŕōũƥś ţō àďď ũśēŕ ţō Warning: Adding the user to the selected group(s) will give them superuser permissions. + Ŵàŕńĩńĝ: Àďďĩńĝ ţĥē ũśēŕ ţō ţĥē śēĺēćţēď ĝŕōũƥ(ś) ŵĩĺĺ ĝĩvē ţĥēm śũƥēŕũśēŕ ƥēŕmĩśśĩōńś. Successfully updated user. + Śũććēśśƒũĺĺŷ ũƥďàţēď ũśēŕ. Successfully created user. + Śũććēśśƒũĺĺŷ ćŕēàţēď ũśēŕ. Username + Ũśēŕńàmē User's primary identifier. 150 characters or fewer. + Ũśēŕ'ś ƥŕĩmàŕŷ ĩďēńţĩƒĩēŕ. 150 ćĥàŕàćţēŕś ōŕ ƒēŵēŕ. User's display name. + Ũśēŕ'ś ďĩśƥĺàŷ ńàmē. Email + Ēmàĩĺ Is active + Ĩś àćţĩvē Designates whether this user should be treated as active. Unselect this instead of deleting accounts. + Ďēśĩĝńàţēś ŵĥēţĥēŕ ţĥĩś ũśēŕ śĥōũĺď ƀē ţŕēàţēď àś àćţĩvē. Ũńśēĺēćţ ţĥĩś ĩńśţēàď ōƒ ďēĺēţĩńĝ àććōũńţś. Path + Ƥàţĥ Policy / User / Group + Ƥōĺĩćŷ / Ũśēŕ / Ĝŕōũƥ Policy + Ƥōĺĩćŷ Group + Ĝŕōũƥ User + Ũśēŕ Edit Policy + Ēďĩţ Ƥōĺĩćŷ Update Group + Ũƥďàţē Ĝŕōũƥ Edit Group + Ēďĩţ Ĝŕōũƥ Update User + Ũƥďàţē Ũśēŕ Edit User + Ēďĩţ Ũśēŕ Policy binding(s) + Ƥōĺĩćŷ ƀĩńďĩńĝ(ś) Update Binding + Ũƥďàţē ßĩńďĩńĝ Edit Binding + Ēďĩţ ßĩńďĩńĝ No Policies bound. + Ńō Ƥōĺĩćĩēś ƀōũńď. No policies are currently bound to this object. + Ńō ƥōĺĩćĩēś àŕē ćũŕŕēńţĺŷ ƀōũńď ţō ţĥĩś ōƀĴēćţ. Bind existing policy + ßĩńď ēxĩśţĩńĝ ƥōĺĩćŷ Warning: Application is not used by any Outpost. + Ŵàŕńĩńĝ: Àƥƥĺĩćàţĩōń ĩś ńōţ ũśēď ƀŷ àńŷ Ōũţƥōśţ. Related + Ŕēĺàţēď Backchannel Providers + ßàćķćĥàńńēĺ Ƥŕōvĩďēŕś Check access + Ćĥēćķ àććēśś Check + Ćĥēćķ Check Application access + Ćĥēćķ Àƥƥĺĩćàţĩōń àććēśś Test + Ţēśţ Launch + Ĺàũńćĥ Logins over the last week (per 8 hours) + Ĺōĝĩńś ōvēŕ ţĥē ĺàśţ ŵēēķ (ƥēŕ 8 ĥōũŕś) Policy / Group / User Bindings + Ƥōĺĩćŷ / Ĝŕōũƥ / Ũśēŕ ßĩńďĩńĝś These policies control which users can access this application. + Ţĥēśē ƥōĺĩćĩēś ćōńţŕōĺ ŵĥĩćĥ ũśēŕś ćàń àććēśś ţĥĩś àƥƥĺĩćàţĩōń. Successfully updated source. + Śũććēśśƒũĺĺŷ ũƥďàţēď śōũŕćē. Successfully created source. + Śũććēśśƒũĺĺŷ ćŕēàţēď śōũŕćē. Sync users + Śŷńć ũśēŕś User password writeback + Ũśēŕ ƥàśśŵōŕď ŵŕĩţēƀàćķ Login password is synced from LDAP into authentik automatically. Enable this option only to write password changes in authentik back to LDAP. + Ĺōĝĩń ƥàśśŵōŕď ĩś śŷńćēď ƒŕōm ĹĎÀƤ ĩńţō àũţĥēńţĩķ àũţōmàţĩćàĺĺŷ. Ēńàƀĺē ţĥĩś ōƥţĩōń ōńĺŷ ţō ŵŕĩţē ƥàśśŵōŕď ćĥàńĝēś ĩń àũţĥēńţĩķ ƀàćķ ţō ĹĎÀƤ. Sync groups + Śŷńć ĝŕōũƥś Connection settings + Ćōńńēćţĩōń śēţţĩńĝś Server URI + Śēŕvēŕ ŨŔĨ Specify multiple server URIs by separating them with a comma. + Śƥēćĩƒŷ mũĺţĩƥĺē śēŕvēŕ ŨŔĨś ƀŷ śēƥàŕàţĩńĝ ţĥēm ŵĩţĥ à ćōmmà. Enable StartTLS + Ēńàƀĺē ŚţàŕţŢĹŚ To use SSL instead, use 'ldaps://' and disable this option. + Ţō ũśē ŚŚĹ ĩńśţēàď, ũśē 'ĺďàƥś://' àńď ďĩśàƀĺē ţĥĩś ōƥţĩōń. TLS Verification Certificate + ŢĹŚ Vēŕĩƒĩćàţĩōń Ćēŕţĩƒĩćàţē When connecting to an LDAP Server with TLS, certificates are not checked by default. Specify a keypair to validate the remote certificate. + Ŵĥēń ćōńńēćţĩńĝ ţō àń ĹĎÀƤ Śēŕvēŕ ŵĩţĥ ŢĹŚ, ćēŕţĩƒĩćàţēś àŕē ńōţ ćĥēćķēď ƀŷ ďēƒàũĺţ. Śƥēćĩƒŷ à ķēŷƥàĩŕ ţō vàĺĩďàţē ţĥē ŕēmōţē ćēŕţĩƒĩćàţē. Bind CN + ßĩńď ĆŃ LDAP Attribute mapping + ĹĎÀƤ Àţţŕĩƀũţē màƥƥĩńĝ Property mappings used to user creation. + Ƥŕōƥēŕţŷ màƥƥĩńĝś ũśēď ţō ũśēŕ ćŕēàţĩōń. Additional settings + Àďďĩţĩōńàĺ śēţţĩńĝś Parent group for all the groups imported from LDAP. + Ƥàŕēńţ ĝŕōũƥ ƒōŕ àĺĺ ţĥē ĝŕōũƥś ĩmƥōŕţēď ƒŕōm ĹĎÀƤ. User path + Ũśēŕ ƥàţĥ Addition User DN + Àďďĩţĩōń Ũśēŕ ĎŃ Additional user DN, prepended to the Base DN. + Àďďĩţĩōńàĺ ũśēŕ ĎŃ, ƥŕēƥēńďēď ţō ţĥē ßàśē ĎŃ. Addition Group DN + Àďďĩţĩōń Ĝŕōũƥ ĎŃ Additional group DN, prepended to the Base DN. + Àďďĩţĩōńàĺ ĝŕōũƥ ĎŃ, ƥŕēƥēńďēď ţō ţĥē ßàśē ĎŃ. User object filter + Ũśēŕ ōƀĴēćţ ƒĩĺţēŕ Consider Objects matching this filter to be Users. + Ćōńśĩďēŕ ŌƀĴēćţś màţćĥĩńĝ ţĥĩś ƒĩĺţēŕ ţō ƀē Ũśēŕś. Group object filter + Ĝŕōũƥ ōƀĴēćţ ƒĩĺţēŕ Consider Objects matching this filter to be Groups. + Ćōńśĩďēŕ ŌƀĴēćţś màţćĥĩńĝ ţĥĩś ƒĩĺţēŕ ţō ƀē Ĝŕōũƥś. Group membership field + Ĝŕōũƥ mēmƀēŕśĥĩƥ ƒĩēĺď Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' + Ƒĩēĺď ŵĥĩćĥ ćōńţàĩńś mēmƀēŕś ōƒ à ĝŕōũƥ. Ńōţē ţĥàţ ĩƒ ũśĩńĝ ţĥē "mēmƀēŕŨĩď" ƒĩēĺď, ţĥē vàĺũē ĩś àśśũmēď ţō ćōńţàĩń à ŕēĺàţĩvē ďĩśţĩńĝũĩśĥēď ńàmē. ē.ĝ. 'mēmƀēŕŨĩď=śōmē-ũśēŕ' ĩńśţēàď ōƒ 'mēmƀēŕŨĩď=ćń=śōmē-ũśēŕ,ōũ=ĝŕōũƥś,...' Object uniqueness field + ŌƀĴēćţ ũńĩǫũēńēśś ƒĩēĺď Field which contains a unique Identifier. + Ƒĩēĺď ŵĥĩćĥ ćōńţàĩńś à ũńĩǫũē Ĩďēńţĩƒĩēŕ. Link users on unique identifier + Ĺĩńķ ũśēŕś ōń ũńĩǫũē ĩďēńţĩƒĩēŕ Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses + Ĺĩńķ ţō à ũśēŕ ŵĩţĥ ĩďēńţĩćàĺ ēmàĩĺ àďďŕēśś. Ćàń ĥàvē śēćũŕĩţŷ ĩmƥĺĩćàţĩōńś ŵĥēń à śōũŕćē ďōēśń'ţ vàĺĩďàţē ēmàĩĺ àďďŕēśśēś Use the user's email address, but deny enrollment when the email address already exists + Ũśē ţĥē ũśēŕ'ś ēmàĩĺ àďďŕēśś, ƀũţ ďēńŷ ēńŕōĺĺmēńţ ŵĥēń ţĥē ēmàĩĺ àďďŕēśś àĺŕēàďŷ ēxĩśţś Link to a user with identical username. Can have security implications when a username is used with another source + Ĺĩńķ ţō à ũśēŕ ŵĩţĥ ĩďēńţĩćàĺ ũśēŕńàmē. Ćàń ĥàvē śēćũŕĩţŷ ĩmƥĺĩćàţĩōńś ŵĥēń à ũśēŕńàmē ĩś ũśēď ŵĩţĥ àńōţĥēŕ śōũŕćē Use the user's username, but deny enrollment when the username already exists + Ũśē ţĥē ũśēŕ'ś ũśēŕńàmē, ƀũţ ďēńŷ ēńŕōĺĺmēńţ ŵĥēń ţĥē ũśēŕńàmē àĺŕēàďŷ ēxĩśţś Unknown user matching mode + Ũńķńōŵń ũśēŕ màţćĥĩńĝ mōďē URL settings + ŨŔĹ śēţţĩńĝś Authorization URL + Àũţĥōŕĩźàţĩōń ŨŔĹ URL the user is redirect to to consent the authorization. + ŨŔĹ ţĥē ũśēŕ ĩś ŕēďĩŕēćţ ţō ţō ćōńśēńţ ţĥē àũţĥōŕĩźàţĩōń. Access token URL + Àććēśś ţōķēń ŨŔĹ URL used by authentik to retrieve tokens. + ŨŔĹ ũśēď ƀŷ àũţĥēńţĩķ ţō ŕēţŕĩēvē ţōķēńś. Profile URL + Ƥŕōƒĩĺē ŨŔĹ URL used by authentik to get user information. + ŨŔĹ ũśēď ƀŷ àũţĥēńţĩķ ţō ĝēţ ũśēŕ ĩńƒōŕmàţĩōń. Request token URL + Ŕēǫũēśţ ţōķēń ŨŔĹ URL used to request the initial token. This URL is only required for OAuth 1. + ŨŔĹ ũśēď ţō ŕēǫũēśţ ţĥē ĩńĩţĩàĺ ţōķēń. Ţĥĩś ŨŔĹ ĩś ōńĺŷ ŕēǫũĩŕēď ƒōŕ ŌÀũţĥ 1. OIDC Well-known URL + ŌĨĎĆ Ŵēĺĺ-ķńōŵń ŨŔĹ OIDC well-known configuration URL. Can be used to automatically configure the URLs above. + ŌĨĎĆ ŵēĺĺ-ķńōŵń ćōńƒĩĝũŕàţĩōń ŨŔĹ. Ćàń ƀē ũśēď ţō àũţōmàţĩćàĺĺŷ ćōńƒĩĝũŕē ţĥē ŨŔĹś àƀōvē. OIDC JWKS URL + ŌĨĎĆ ĵŴĶŚ ŨŔĹ JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source. + ĵŚŌŃ Ŵēƀ Ķēŷ ŨŔĹ. Ķēŷś ƒŕōm ţĥē ŨŔĹ ŵĩĺĺ ƀē ũśēď ţō vàĺĩďàţē ĵŴŢś ƒŕōm ţĥĩś śōũŕćē. OIDC JWKS + ŌĨĎĆ ĵŴĶŚ Raw JWKS data. + Ŕàŵ ĵŴĶŚ ďàţà. User matching mode + Ũśēŕ màţćĥĩńĝ mōďē Delete currently set icon. + Ďēĺēţē ćũŕŕēńţĺŷ śēţ ĩćōń. Consumer key + Ćōńśũmēŕ ķēŷ Consumer secret + Ćōńśũmēŕ śēćŕēţ Additional scopes to be passed to the OAuth Provider, separated by space. To replace existing scopes, prefix with *. + Àďďĩţĩōńàĺ śćōƥēś ţō ƀē ƥàśśēď ţō ţĥē ŌÀũţĥ Ƥŕōvĩďēŕ, śēƥàŕàţēď ƀŷ śƥàćē. Ţō ŕēƥĺàćē ēxĩśţĩńĝ śćōƥēś, ƥŕēƒĩx ŵĩţĥ *. Flow settings + Ƒĺōŵ śēţţĩńĝś Flow to use when authenticating existing users. + Ƒĺōŵ ţō ũśē ŵĥēń àũţĥēńţĩćàţĩńĝ ēxĩśţĩńĝ ũśēŕś. Enrollment flow + Ēńŕōĺĺmēńţ ƒĺōŵ Flow to use when enrolling new users. + Ƒĺōŵ ţō ũśē ŵĥēń ēńŕōĺĺĩńĝ ńēŵ ũśēŕś. Load servers + Ĺōàď śēŕvēŕś Re-authenticate with plex + Ŕē-àũţĥēńţĩćàţē ŵĩţĥ ƥĺēx Allow friends to authenticate via Plex, even if you don't share any servers + Àĺĺōŵ ƒŕĩēńďś ţō àũţĥēńţĩćàţē vĩà Ƥĺēx, ēvēń ĩƒ ŷōũ ďōń'ţ śĥàŕē àńŷ śēŕvēŕś Allowed servers + Àĺĺōŵēď śēŕvēŕś Select which server a user has to be a member of to be allowed to authenticate. + Śēĺēćţ ŵĥĩćĥ śēŕvēŕ à ũśēŕ ĥàś ţō ƀē à mēmƀēŕ ōƒ ţō ƀē àĺĺōŵēď ţō àũţĥēńţĩćàţē. SSO URL + ŚŚŌ ŨŔĹ URL that the initial Login request is sent to. + ŨŔĹ ţĥàţ ţĥē ĩńĩţĩàĺ Ĺōĝĩń ŕēǫũēśţ ĩś śēńţ ţō. SLO URL + ŚĹŌ ŨŔĹ Optional URL if the IDP supports Single-Logout. + Ōƥţĩōńàĺ ŨŔĹ ĩƒ ţĥē ĨĎƤ śũƥƥōŕţś Śĩńĝĺē-Ĺōĝōũţ. Also known as Entity ID. Defaults the Metadata URL. + Àĺśō ķńōŵń àś Ēńţĩţŷ ĨĎ. Ďēƒàũĺţś ţĥē Mēţàďàţà ŨŔĹ. Binding Type + ßĩńďĩńĝ Ţŷƥē Redirect binding + Ŕēďĩŕēćţ ƀĩńďĩńĝ Post-auto binding + Ƥōśţ-àũţō ƀĩńďĩńĝ Post binding but the request is automatically sent and the user doesn't have to confirm. + Ƥōśţ ƀĩńďĩńĝ ƀũţ ţĥē ŕēǫũēśţ ĩś àũţōmàţĩćàĺĺŷ śēńţ àńď ţĥē ũśēŕ ďōēśń'ţ ĥàvē ţō ćōńƒĩŕm. Post binding + Ƥōśţ ƀĩńďĩńĝ Signing keypair + Śĩĝńĩńĝ ķēŷƥàĩŕ Keypair which is used to sign outgoing requests. Leave empty to disable signing. + Ķēŷƥàĩŕ ŵĥĩćĥ ĩś ũśēď ţō śĩĝń ōũţĝōĩńĝ ŕēǫũēśţś. Ĺēàvē ēmƥţŷ ţō ďĩśàƀĺē śĩĝńĩńĝ. Allow IDP-initiated logins + Àĺĺōŵ ĨĎƤ-ĩńĩţĩàţēď ĺōĝĩńś Allows authentication flows initiated by the IdP. This can be a security risk, as no validation of the request ID is done. + Àĺĺōŵś àũţĥēńţĩćàţĩōń ƒĺōŵś ĩńĩţĩàţēď ƀŷ ţĥē ĨďƤ. Ţĥĩś ćàń ƀē à śēćũŕĩţŷ ŕĩśķ, àś ńō vàĺĩďàţĩōń ōƒ ţĥē ŕēǫũēśţ ĨĎ ĩś ďōńē. NameID Policy + ŃàmēĨĎ Ƥōĺĩćŷ Persistent + Ƥēŕśĩśţēńţ Email address + Ēmàĩĺ àďďŕēśś Windows + Ŵĩńďōŵś X509 Subject + X509 ŚũƀĴēćţ Transient + Ţŕàńśĩēńţ Delete temporary users after + Ďēĺēţē ţēmƥōŕàŕŷ ũśēŕś àƒţēŕ Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. + Ţĩmē ōƒƒśēţ ŵĥēń ţēmƥōŕàŕŷ ũśēŕś śĥōũĺď ƀē ďēĺēţēď. Ţĥĩś ōńĺŷ àƥƥĺĩēś ĩƒ ŷōũŕ ĨĎƤ ũśēś ţĥē ŃàmēĨĎ Ƒōŕmàţ 'ţŕàńśĩēńţ', àńď ţĥē ũśēŕ ďōēśń'ţ ĺōĝ ōũţ màńũàĺĺŷ. Pre-authentication flow + Ƥŕē-àũţĥēńţĩćàţĩōń ƒĺōŵ Flow used before authentication. + Ƒĺōŵ ũśēď ƀēƒōŕē àũţĥēńţĩćàţĩōń. New source + Ńēŵ śōũŕćē Create a new source. + Ćŕēàţē à ńēŵ śōũŕćē. Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves. + Śōũŕćēś ōƒ ĩďēńţĩţĩēś, ŵĥĩćĥ ćàń ēĩţĥēŕ ƀē śŷńćēď ĩńţō àũţĥēńţĩķ'ś ďàţàƀàśē, ōŕ ćàń ƀē ũśēď ƀŷ ũśēŕś ţō àũţĥēńţĩćàţē àńď ēńŕōĺĺ ţĥēmśēĺvēś. Source(s) + Śōũŕćē(ś) Disabled + Ďĩśàƀĺēď Built-in + ßũĩĺţ-ĩń Update LDAP Source + Ũƥďàţē ĹĎÀƤ Śōũŕćē Not synced yet. + Ńōţ śŷńćēď ŷēţ. Task finished with warnings + Ţàśķ ƒĩńĩśĥēď ŵĩţĥ ŵàŕńĩńĝś Task finished with errors + Ţàśķ ƒĩńĩśĥēď ŵĩţĥ ēŕŕōŕś Last sync: + Ĺàśţ śŷńć: OAuth Source + ŌÀũţĥ Śōũŕćē Generic OpenID Connect + Ĝēńēŕĩć ŌƥēńĨĎ Ćōńńēćţ Unknown provider type + Ũńķńōŵń ƥŕōvĩďēŕ ţŷƥē Details + Ďēţàĩĺś Callback URL + Ćàĺĺƀàćķ ŨŔĹ Access Key + Àććēśś Ķēŷ Update OAuth Source + Ũƥďàţē ŌÀũţĥ Śōũŕćē Diagram + Ďĩàĝŕàm Policy Bindings + Ƥōĺĩćŷ ßĩńďĩńĝś These bindings control which users can access this source. You can only use policies here as access is checked before the user is authenticated. + Ţĥēśē ƀĩńďĩńĝś ćōńţŕōĺ ŵĥĩćĥ ũśēŕś ćàń àććēśś ţĥĩś śōũŕćē. + Ŷōũ ćàń ōńĺŷ ũśē ƥōĺĩćĩēś ĥēŕē àś àććēśś ĩś ćĥēćķēď ƀēƒōŕē ţĥē ũśēŕ ĩś àũţĥēńţĩćàţēď. Update Plex Source + Ũƥďàţē Ƥĺēx Śōũŕćē Update SAML Source + Ũƥďàţē ŚÀMĹ Śōũŕćē Successfully updated mapping. + Śũććēśśƒũĺĺŷ ũƥďàţēď màƥƥĩńĝ. Successfully created mapping. + Śũććēśśƒũĺĺŷ ćŕēàţēď màƥƥĩńĝ. Object field + ŌƀĴēćţ ƒĩēĺď Field of the user object this value is written to. + Ƒĩēĺď ōƒ ţĥē ũśēŕ ōƀĴēćţ ţĥĩś vàĺũē ĩś ŵŕĩţţēń ţō. SAML Attribute Name + ŚÀMĹ Àţţŕĩƀũţē Ńàmē Attribute name used for SAML Assertions. Can be a URN OID, a schema reference, or a any other string. If this property mapping is used for NameID Property, this field is discarded. + Àţţŕĩƀũţē ńàmē ũśēď ƒōŕ ŚÀMĹ Àśśēŕţĩōńś. Ćàń ƀē à ŨŔŃ ŌĨĎ, à śćĥēmà ŕēƒēŕēńćē, ōŕ à àńŷ ōţĥēŕ śţŕĩńĝ. Ĩƒ ţĥĩś ƥŕōƥēŕţŷ màƥƥĩńĝ ĩś ũśēď ƒōŕ ŃàmēĨĎ Ƥŕōƥēŕţŷ, ţĥĩś ƒĩēĺď ĩś ďĩśćàŕďēď. Friendly Name + Ƒŕĩēńďĺŷ Ńàmē Optionally set the 'FriendlyName' value of the Assertion attribute. + Ōƥţĩōńàĺĺŷ śēţ ţĥē 'ƑŕĩēńďĺŷŃàmē' vàĺũē ōƒ ţĥē Àśśēŕţĩōń àţţŕĩƀũţē. Scope name + Śćōƥē ńàmē Scope which the client can specify to access these properties. + Śćōƥē ŵĥĩćĥ ţĥē ćĺĩēńţ ćàń śƥēćĩƒŷ ţō àććēśś ţĥēśē ƥŕōƥēŕţĩēś. Description shown to the user when consenting. If left empty, the user won't be informed. + Ďēśćŕĩƥţĩōń śĥōŵń ţō ţĥē ũśēŕ ŵĥēń ćōńśēńţĩńĝ. Ĩƒ ĺēƒţ ēmƥţŷ, ţĥē ũśēŕ ŵōń'ţ ƀē ĩńƒōŕmēď. Example context data + Ēxàmƥĺē ćōńţēxţ ďàţà Active Directory User + Àćţĩvē Ďĩŕēćţōŕŷ Ũśēŕ Active Directory Group + Àćţĩvē Ďĩŕēćţōŕŷ Ĝŕōũƥ New property mapping + Ńēŵ ƥŕōƥēŕţŷ màƥƥĩńĝ Create a new property mapping. + Ćŕēàţē à ńēŵ ƥŕōƥēŕţŷ màƥƥĩńĝ. Property Mappings + Ƥŕōƥēŕţŷ Màƥƥĩńĝś Control how authentik exposes and interprets information. + Ćōńţŕōĺ ĥōŵ àũţĥēńţĩķ ēxƥōśēś àńď ĩńţēŕƥŕēţś ĩńƒōŕmàţĩōń. Property Mapping(s) + Ƥŕōƥēŕţŷ Màƥƥĩńĝ(ś) Test Property Mapping + Ţēśţ Ƥŕōƥēŕţŷ Màƥƥĩńĝ Hide managed mappings + Ĥĩďē màńàĝēď màƥƥĩńĝś Successfully updated token. + Śũććēśśƒũĺĺŷ ũƥďàţēď ţōķēń. Successfully created token. + Śũććēśśƒũĺĺŷ ćŕēàţēď ţōķēń. Unique identifier the token is referenced by. + Ũńĩǫũē ĩďēńţĩƒĩēŕ ţĥē ţōķēń ĩś ŕēƒēŕēńćēď ƀŷ. Intent + Ĩńţēńţ API Token + ÀƤĨ Ţōķēń Used to access the API programmatically + Ũśēď ţō àććēśś ţĥē ÀƤĨ ƥŕōĝŕàmmàţĩćàĺĺŷ App password. + Àƥƥ ƥàśśŵōŕď. Used to login using a flow executor + Ũśēď ţō ĺōĝĩń ũśĩńĝ à ƒĺōŵ ēxēćũţōŕ Expiring + Ēxƥĩŕĩńĝ If this is selected, the token will expire. Upon expiration, the token will be rotated. + Ĩƒ ţĥĩś ĩś śēĺēćţēď, ţĥē ţōķēń ŵĩĺĺ ēxƥĩŕē. Ũƥōń ēxƥĩŕàţĩōń, ţĥē ţōķēń ŵĩĺĺ ƀē ŕōţàţēď. Expires on + Ēxƥĩŕēś ōń API Access + ÀƤĨ Àććēśś App password + Àƥƥ ƥàśśŵōŕď Verification + Vēŕĩƒĩćàţĩōń Unknown intent + Ũńķńōŵń ĩńţēńţ Tokens + Ţōķēńś Tokens are used throughout authentik for Email validation stages, Recovery keys and API access. + Ţōķēńś àŕē ũśēď ţĥŕōũĝĥōũţ àũţĥēńţĩķ ƒōŕ Ēmàĩĺ vàĺĩďàţĩōń śţàĝēś, Ŕēćōvēŕŷ ķēŷś àńď ÀƤĨ àććēśś. Expires? + Ēxƥĩŕēś? Expiry date + Ēxƥĩŕŷ ďàţē Token(s) + Ţōķēń(ś) Create Token + Ćŕēàţē Ţōķēń Token is managed by authentik. + Ţōķēń ĩś màńàĝēď ƀŷ àũţĥēńţĩķ. Update Token + Ũƥďàţē Ţōķēń Successfully updated tenant. + Śũććēśśƒũĺĺŷ ũƥďàţēď ţēńàńţ. Successfully created tenant. + Śũććēśśƒũĺĺŷ ćŕēàţēď ţēńàńţ. Domain + Ďōmàĩń Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match. + Màţćĥĩńĝ ĩś ďōńē ƀàśēď ōń ďōmàĩń śũƒƒĩx, śō ĩƒ ŷōũ ēńţēŕ ďōmàĩń.ţĺď, ƒōō.ďōmàĩń.ţĺď ŵĩĺĺ śţĩĺĺ màţćĥ. Default + Ďēƒàũĺţ Use this tenant for each domain that doesn't have a dedicated tenant. + Ũśē ţĥĩś ţēńàńţ ƒōŕ ēàćĥ ďōmàĩń ţĥàţ ďōēśń'ţ ĥàvē à ďēďĩćàţēď ţēńàńţ. Branding settings + ßŕàńďĩńĝ śēţţĩńĝś Title + Ţĩţĺē Branding shown in page title and several other places. + ßŕàńďĩńĝ śĥōŵń ĩń ƥàĝē ţĩţĺē àńď śēvēŕàĺ ōţĥēŕ ƥĺàćēś. Logo + Ĺōĝō Icon shown in sidebar/header and flow executor. + Ĩćōń śĥōŵń ĩń śĩďēƀàŕ/ĥēàďēŕ àńď ƒĺōŵ ēxēćũţōŕ. Favicon + Ƒàvĩćōń Icon shown in the browser tab. + Ĩćōń śĥōŵń ĩń ţĥē ƀŕōŵśēŕ ţàƀ. Default flows + Ďēƒàũĺţ ƒĺōŵś Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used. + Ƒĺōŵ ũśēď ţō àũţĥēńţĩćàţē ũśēŕś. Ĩƒ ĺēƒţ ēmƥţŷ, ţĥē ƒĩŕśţ àƥƥĺĩćàƀĺē ƒĺōŵ śōŕţēď ƀŷ ţĥē śĺũĝ ĩś ũśēď. Invalidation flow + Ĩńvàĺĩďàţĩōń ƒĺōŵ Flow used to logout. If left empty, the first applicable flow sorted by the slug is used. + Ƒĺōŵ ũśēď ţō ĺōĝōũţ. Ĩƒ ĺēƒţ ēmƥţŷ, ţĥē ƒĩŕśţ àƥƥĺĩćàƀĺē ƒĺōŵ śōŕţēď ƀŷ ţĥē śĺũĝ ĩś ũśēď. Recovery flow + Ŕēćōvēŕŷ ƒĺōŵ Recovery flow. If left empty, the first applicable flow sorted by the slug is used. + Ŕēćōvēŕŷ ƒĺōŵ. Ĩƒ ĺēƒţ ēmƥţŷ, ţĥē ƒĩŕśţ àƥƥĺĩćàƀĺē ƒĺōŵ śōŕţēď ƀŷ ţĥē śĺũĝ ĩś ũśēď. Unenrollment flow + Ũńēńŕōĺĺmēńţ ƒĺōŵ If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown. + Ĩƒ śēţ, ũśēŕś àŕē àƀĺē ţō ũńēńŕōĺĺ ţĥēmśēĺvēś ũśĩńĝ ţĥĩś ƒĺōŵ. Ĩƒ ńō ƒĺōŵ ĩś śēţ, ōƥţĩōń ĩś ńōţ śĥōŵń. User settings flow + Ũśēŕ śēţţĩńĝś ƒĺōŵ If set, users are able to configure details of their profile. + Ĩƒ śēţ, ũśēŕś àŕē àƀĺē ţō ćōńƒĩĝũŕē ďēţàĩĺś ōƒ ţĥēĩŕ ƥŕōƒĩĺē. Device code flow + Ďēvĩćē ćōďē ƒĺōŵ If set, the OAuth Device Code profile can be used, and the selected flow will be used to enter the code. + Ĩƒ śēţ, ţĥē ŌÀũţĥ Ďēvĩćē Ćōďē ƥŕōƒĩĺē ćàń ƀē ũśēď, àńď ţĥē śēĺēćţēď ƒĺōŵ ŵĩĺĺ ƀē ũśēď ţō ēńţēŕ ţĥē ćōďē. Other global settings + Ōţĥēŕ ĝĺōƀàĺ śēţţĩńĝś Web Certificate + Ŵēƀ Ćēŕţĩƒĩćàţē Event retention + Ēvēńţ ŕēţēńţĩōń Duration after which events will be deleted from the database. + Ďũŕàţĩōń àƒţēŕ ŵĥĩćĥ ēvēńţś ŵĩĺĺ ƀē ďēĺēţēď ƒŕōm ţĥē ďàţàƀàśē. When using an external logging solution for archiving, this can be set to "minutes=5". + Ŵĥēń ũśĩńĝ àń ēxţēŕńàĺ ĺōĝĝĩńĝ śōĺũţĩōń ƒōŕ àŕćĥĩvĩńĝ, ţĥĩś ćàń ƀē śēţ ţō "mĩńũţēś=5". This setting only affects new Events, as the expiration is saved per-event. + Ţĥĩś śēţţĩńĝ ōńĺŷ àƒƒēćţś ńēŵ Ēvēńţś, àś ţĥē ēxƥĩŕàţĩōń ĩś śàvēď ƥēŕ-ēvēńţ. Format: "weeks=3;days=2;hours=3,seconds=2". + Ƒōŕmàţ: "ŵēēķś=3;ďàŷś=2;ĥōũŕś=3,śēćōńďś=2". Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this tenant. + Śēţ ćũśţōm àţţŕĩƀũţēś ũśĩńĝ ŶÀMĹ ōŕ ĵŚŌŃ. Àńŷ àţţŕĩƀũţēś śēţ ĥēŕē ŵĩĺĺ ƀē ĩńĥēŕĩţēď ƀŷ ũśēŕś, ĩƒ ţĥē ŕēǫũēśţ ĩś ĥàńďĺēď ƀŷ ţĥĩś ţēńàńţ. Tenants + Ţēńàńţś Configure visual settings and defaults for different domains. + Ćōńƒĩĝũŕē vĩśũàĺ śēţţĩńĝś àńď ďēƒàũĺţś ƒōŕ ďĩƒƒēŕēńţ ďōmàĩńś. Default? + Ďēƒàũĺţ? Tenant(s) + Ţēńàńţ(ś) Update Tenant + Ũƥďàţē Ţēńàńţ Create Tenant + Ćŕēàţē Ţēńàńţ Policies + Ƥōĺĩćĩēś Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages. + Àĺĺōŵ ũśēŕś ţō ũśē Àƥƥĺĩćàţĩōńś ƀàśēď ōń ƥŕōƥēŕţĩēś, ēńƒōŕćē Ƥàśśŵōŕď Ćŕĩţēŕĩà àńď śēĺēćţĩvēĺŷ àƥƥĺŷ Śţàĝēś. Assigned to object(s). + Àśśĩĝńēď ţō ōƀĴēćţ(ś). Warning: Policy is not assigned. + Ŵàŕńĩńĝ: Ƥōĺĩćŷ ĩś ńōţ àśśĩĝńēď. Test Policy + Ţēśţ Ƥōĺĩćŷ Policy / Policies + Ƥōĺĩćŷ / Ƥōĺĩćĩēś Successfully cleared policy cache + Śũććēśśƒũĺĺŷ ćĺēàŕēď ƥōĺĩćŷ ćàćĥē Failed to delete policy cache + Ƒàĩĺēď ţō ďēĺēţē ƥōĺĩćŷ ćàćĥē Clear cache + Ćĺēàŕ ćàćĥē Clear Policy cache + Ćĺēàŕ Ƥōĺĩćŷ ćàćĥē Are you sure you want to clear the policy cache? This will cause all policies to be re-evaluated on their next usage. + Àŕē ŷōũ śũŕē ŷōũ ŵàńţ ţō ćĺēàŕ ţĥē ƥōĺĩćŷ ćàćĥē? Ţĥĩś ŵĩĺĺ ćàũśē àĺĺ ƥōĺĩćĩēś ţō ƀē ŕē-ēvàĺũàţēď ōń ţĥēĩŕ ńēxţ ũśàĝē. Reputation scores + Ŕēƥũţàţĩōń śćōŕēś Reputation for IP and user identifiers. Scores are decreased for each failed login and increased for each successful login. + Ŕēƥũţàţĩōń ƒōŕ ĨƤ àńď ũśēŕ ĩďēńţĩƒĩēŕś. Śćōŕēś àŕē ďēćŕēàśēď ƒōŕ ēàćĥ ƒàĩĺēď ĺōĝĩń àńď ĩńćŕēàśēď ƒōŕ ēàćĥ śũććēśśƒũĺ ĺōĝĩń. IP + ĨƤ Score + Śćōŕē Updated + Ũƥďàţēď Reputation + Ŕēƥũţàţĩōń Groups + Ĝŕōũƥś Group users together and give them permissions based on the membership. + Ĝŕōũƥ ũśēŕś ţōĝēţĥēŕ àńď ĝĩvē ţĥēm ƥēŕmĩśśĩōńś ƀàśēď ōń ţĥē mēmƀēŕśĥĩƥ. Superuser privileges? + Śũƥēŕũśēŕ ƥŕĩvĩĺēĝēś? Group(s) + Ĝŕōũƥ(ś) Create Group + Ćŕēàţē Ĝŕōũƥ Create group + Ćŕēàţē ĝŕōũƥ Enabling this toggle will create a group named after the user, with the user as member. + Ēńàƀĺĩńĝ ţĥĩś ţōĝĝĺē ŵĩĺĺ ćŕēàţē à ĝŕōũƥ ńàmēď àƒţēŕ ţĥē ũśēŕ, ŵĩţĥ ţĥē ũśēŕ àś mēmƀēŕ. Use the username and password below to authenticate. The password can be retrieved later on the Tokens page. + Ũśē ţĥē ũśēŕńàmē àńď ƥàśśŵōŕď ƀēĺōŵ ţō àũţĥēńţĩćàţē. Ţĥē ƥàśśŵōŕď ćàń ƀē ŕēţŕĩēvēď ĺàţēŕ ōń ţĥē Ţōķēńś ƥàĝē. Password + Ƥàśśŵōŕď Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List. + Vàĺĩď ƒōŕ 360 ďàŷś, àƒţēŕ ŵĥĩćĥ ţĥē ƥàśśŵōŕď ŵĩĺĺ àũţōmàţĩćàĺĺŷ ŕōţàţē. Ŷōũ ćàń ćōƥŷ ţĥē ƥàśśŵōŕď ƒŕōm ţĥē Ţōķēń Ĺĩśţ. The following objects use + Ţĥē ƒōĺĺōŵĩńĝ ōƀĴēćţś ũśē connecting object will be deleted + ćōńńēćţĩńĝ ōƀĴēćţ ŵĩĺĺ ƀē ďēĺēţēď Successfully updated + Śũććēśśƒũĺĺŷ ũƥďàţēď Failed to update : + Ƒàĩĺēď ţō ũƥďàţē : Are you sure you want to update ""? + Àŕē ŷōũ śũŕē ŷōũ ŵàńţ ţō ũƥďàţē ""? Successfully updated password. + Śũććēśśƒũĺĺŷ ũƥďàţēď ƥàśśŵōŕď. Successfully sent email. + Śũććēśśƒũĺĺŷ śēńţ ēmàĩĺ. Email stage + Ēmàĩĺ śţàĝē Successfully added user(s). + Śũććēśśƒũĺĺŷ àďďēď ũśēŕ(ś). Users to add + Ũśēŕś ţō àďď User(s) + Ũśēŕ(ś) Remove Users(s) + Ŕēmōvē Ũśēŕś(ś) Are you sure you want to remove the selected users from the group ? + Àŕē ŷōũ śũŕē ŷōũ ŵàńţ ţō ŕēmōvē ţĥē śēĺēćţēď ũśēŕś ƒŕōm ţĥē ĝŕōũƥ ? Remove + Ŕēmōvē Impersonate + Ĩmƥēŕśōńàţē User status + Ũśēŕ śţàţũś Change status + Ćĥàńĝē śţàţũś Deactivate + Ďēàćţĩvàţē Update password + Ũƥďàţē ƥàśśŵōŕď Set password + Śēţ ƥàśśŵōŕď Successfully generated recovery link + Śũććēśśƒũĺĺŷ ĝēńēŕàţēď ŕēćōvēŕŷ ĺĩńķ No recovery flow is configured. + Ńō ŕēćōvēŕŷ ƒĺōŵ ĩś ćōńƒĩĝũŕēď. Copy recovery link + Ćōƥŷ ŕēćōvēŕŷ ĺĩńķ Send link + Śēńď ĺĩńķ Send recovery link to user + Śēńď ŕēćōvēŕŷ ĺĩńķ ţō ũśēŕ Email recovery link + Ēmàĩĺ ŕēćōvēŕŷ ĺĩńķ Recovery link cannot be emailed, user has no email address saved. + Ŕēćōvēŕŷ ĺĩńķ ćàńńōţ ƀē ēmàĩĺēď, ũśēŕ ĥàś ńō ēmàĩĺ àďďŕēśś śàvēď. To let a user directly reset a their password, configure a recovery flow on the currently active tenant. + Ţō ĺēţ à ũśēŕ ďĩŕēćţĺŷ ŕēśēţ à ţĥēĩŕ ƥàśśŵōŕď, ćōńƒĩĝũŕē à ŕēćōvēŕŷ ƒĺōŵ ōń ţĥē ćũŕŕēńţĺŷ àćţĩvē ţēńàńţ. Add User + Àďď Ũśēŕ Warning: This group is configured with superuser access. Added users will have superuser access. + Ŵàŕńĩńĝ: Ţĥĩś ĝŕōũƥ ĩś ćōńƒĩĝũŕēď ŵĩţĥ śũƥēŕũśēŕ àććēśś. Àďďēď ũśēŕś ŵĩĺĺ ĥàvē śũƥēŕũśēŕ àććēśś. Add existing user + Àďď ēxĩśţĩńĝ ũśēŕ Create user + Ćŕēàţē ũśēŕ Create User + Ćŕēàţē Ũśēŕ Create Service account + Ćŕēàţē Śēŕvĩćē àććōũńţ Hide service-accounts + Ĥĩďē śēŕvĩćē-àććōũńţś Group Info + Ĝŕōũƥ Ĩńƒō Notes + Ńōţēś Edit the notes attribute of this group to add notes here. + Ēďĩţ ţĥē ńōţēś àţţŕĩƀũţē ōƒ ţĥĩś ĝŕōũƥ ţō àďď ńōţēś ĥēŕē. Users + Ũśēŕś Root + Ŕōōţ Warning: You're about to delete the user you're logged in as (). Proceed at your own risk. + Ŵàŕńĩńĝ: Ŷōũ'ŕē àƀōũţ ţō ďēĺēţē ţĥē ũśēŕ ŷōũ'ŕē ĺōĝĝēď ĩń àś (). Ƥŕōćēēď àţ ŷōũŕ ōŵń ŕĩśķ. Hide deactivated user + Ĥĩďē ďēàćţĩvàţēď ũśēŕ User folders + Ũśēŕ ƒōĺďēŕś Successfully added user to group(s). + Śũććēśśƒũĺĺŷ àďďēď ũśēŕ ţō ĝŕōũƥ(ś). Groups to add + Ĝŕōũƥś ţō àďď Remove from Group(s) + Ŕēmōvē ƒŕōm Ĝŕōũƥ(ś) Are you sure you want to remove user from the following groups? + Àŕē ŷōũ śũŕē ŷōũ ŵàńţ ţō ŕēmōvē ũśēŕ ƒŕōm ţĥē ƒōĺĺōŵĩńĝ ĝŕōũƥś? Add Group + Àďď Ĝŕōũƥ Add to existing group + Àďď ţō ēxĩśţĩńĝ ĝŕōũƥ Add new group + Àďď ńēŵ ĝŕōũƥ Application authorizations + Àƥƥĺĩćàţĩōń àũţĥōŕĩźàţĩōńś Revoked? + Ŕēvōķēď? Expires + Ēxƥĩŕēś ID Token + ĨĎ Ţōķēń Refresh Tokens(s) + Ŕēƒŕēśĥ Ţōķēńś(ś) Last IP + Ĺàśţ ĨƤ Session(s) + Śēśśĩōń(ś) Expiry + Ēxƥĩŕŷ (Current session) + (Ćũŕŕēńţ śēśśĩōń) Permissions + Ƥēŕmĩśśĩōńś Consent(s) + Ćōńśēńţ(ś) Successfully updated device. + Śũććēśśƒũĺĺŷ ũƥďàţēď ďēvĩćē. Static tokens + Śţàţĩć ţōķēńś TOTP Device + ŢŌŢƤ Ďēvĩćē Enroll + Ēńŕōĺĺ Device(s) + Ďēvĩćē(ś) Update Device + Ũƥďàţē Ďēvĩćē Confirmed + Ćōńƒĩŕmēď User Info + Ũśēŕ Ĩńƒō To create a recovery link, the current tenant needs to have a recovery flow configured. + Ţō ćŕēàţē à ŕēćōvēŕŷ ĺĩńķ, ţĥē ćũŕŕēńţ ţēńàńţ ńēēďś ţō ĥàvē à ŕēćōvēŕŷ ƒĺōŵ ćōńƒĩĝũŕēď. Reset Password + Ŕēśēţ Ƥàśśŵōŕď Actions over the last week (per 8 hours) + Àćţĩōńś ōvēŕ ţĥē ĺàśţ ŵēēķ (ƥēŕ 8 ĥōũŕś) Edit the notes attribute of this user to add notes here. + Ēďĩţ ţĥē ńōţēś àţţŕĩƀũţē ōƒ ţĥĩś ũśēŕ ţō àďď ńōţēś ĥēŕē. Sessions + Śēśśĩōńś User events + Ũśēŕ ēvēńţś Explicit Consent + Ēxƥĺĩćĩţ Ćōńśēńţ OAuth Refresh Tokens + ŌÀũţĥ Ŕēƒŕēśĥ Ţōķēńś MFA Authenticators + MƑÀ Àũţĥēńţĩćàţōŕś Successfully updated invitation. + Śũććēśśƒũĺĺŷ ũƥďàţēď ĩńvĩţàţĩōń. Successfully created invitation. + Śũććēśśƒũĺĺŷ ćŕēàţēď ĩńvĩţàţĩōń. Flow + Ƒĺōŵ When selected, the invite will only be usable with the flow. By default the invite is accepted on all flows with invitation stages. + Ŵĥēń śēĺēćţēď, ţĥē ĩńvĩţē ŵĩĺĺ ōńĺŷ ƀē ũśàƀĺē ŵĩţĥ ţĥē ƒĺōŵ. ßŷ ďēƒàũĺţ ţĥē ĩńvĩţē ĩś àććēƥţēď ōń àĺĺ ƒĺōŵś ŵĩţĥ ĩńvĩţàţĩōń śţàĝēś. Optional data which is loaded into the flow's 'prompt_data' context variable. YAML or JSON. + Ōƥţĩōńàĺ ďàţà ŵĥĩćĥ ĩś ĺōàďēď ĩńţō ţĥē ƒĺōŵ'ś 'ƥŕōmƥţ_ďàţà' ćōńţēxţ vàŕĩàƀĺē. ŶÀMĹ ōŕ ĵŚŌŃ. Single use + Śĩńĝĺē ũśē When enabled, the invitation will be deleted after usage. + Ŵĥēń ēńàƀĺēď, ţĥē ĩńvĩţàţĩōń ŵĩĺĺ ƀē ďēĺēţēď àƒţēŕ ũśàĝē. Select an enrollment flow + Śēĺēćţ àń ēńŕōĺĺmēńţ ƒĺōŵ Link to use the invitation. + Ĺĩńķ ţō ũśē ţĥē ĩńvĩţàţĩōń. Invitations + Ĩńvĩţàţĩōńś Create Invitation Links to enroll Users, and optionally force specific attributes of their account. + Ćŕēàţē Ĩńvĩţàţĩōń Ĺĩńķś ţō ēńŕōĺĺ Ũśēŕś, àńď ōƥţĩōńàĺĺŷ ƒōŕćē śƥēćĩƒĩć àţţŕĩƀũţēś ōƒ ţĥēĩŕ àććōũńţ. Created by + Ćŕēàţēď ƀŷ Invitation(s) + Ĩńvĩţàţĩōń(ś) Invitation not limited to any flow, and can be used with any enrollment flow. + Ĩńvĩţàţĩōń ńōţ ĺĩmĩţēď ţō àńŷ ƒĺōŵ, àńď ćàń ƀē ũśēď ŵĩţĥ àńŷ ēńŕōĺĺmēńţ ƒĺōŵ. Update Invitation + Ũƥďàţē Ĩńvĩţàţĩōń Create Invitation + Ćŕēàţē Ĩńvĩţàţĩōń Warning: No invitation stage is bound to any flow. Invitations will not work as expected. + Ŵàŕńĩńĝ: Ńō ĩńvĩţàţĩōń śţàĝē ĩś ƀōũńď ţō àńŷ ƒĺōŵ. Ĩńvĩţàţĩōńś ŵĩĺĺ ńōţ ŵōŕķ àś ēxƥēćţēď. Auto-detect (based on your browser) + Àũţō-ďēţēćţ (ƀàśēď ōń ŷōũŕ ƀŕōŵśēŕ) Required. + Ŕēǫũĩŕēď. Continue + Ćōńţĩńũē Successfully updated prompt. + Śũććēśśƒũĺĺŷ ũƥďàţēď ƥŕōmƥţ. Successfully created prompt. + Śũććēśśƒũĺĺŷ ćŕēàţēď ƥŕōmƥţ. Text: Simple Text input + Ţēxţ: Śĩmƥĺē Ţēxţ ĩńƥũţ Text Area: Multiline text input + Ţēxţ Àŕēà: Mũĺţĩĺĩńē ţēxţ ĩńƥũţ Text (read-only): Simple Text input, but cannot be edited. + Ţēxţ (ŕēàď-ōńĺŷ): Śĩmƥĺē Ţēxţ ĩńƥũţ, ƀũţ ćàńńōţ ƀē ēďĩţēď. Text Area (read-only): Multiline text input, but cannot be edited. + Ţēxţ Àŕēà (ŕēàď-ōńĺŷ): Mũĺţĩĺĩńē ţēxţ ĩńƥũţ, ƀũţ ćàńńōţ ƀē ēďĩţēď. Username: Same as Text input, but checks for and prevents duplicate usernames. + Ũśēŕńàmē: Śàmē àś Ţēxţ ĩńƥũţ, ƀũţ ćĥēćķś ƒōŕ àńď ƥŕēvēńţś ďũƥĺĩćàţē ũśēŕńàmēś. Email: Text field with Email type. + Ēmàĩĺ: Ţēxţ ƒĩēĺď ŵĩţĥ Ēmàĩĺ ţŷƥē. Password: Masked input, multiple inputs of this type on the same prompt need to be identical. + Ƥàśśŵōŕď: Màśķēď ĩńƥũţ, mũĺţĩƥĺē ĩńƥũţś ōƒ ţĥĩś ţŷƥē ōń ţĥē śàmē ƥŕōmƥţ ńēēď ţō ƀē ĩďēńţĩćàĺ. Number + Ńũmƀēŕ Checkbox + Ćĥēćķƀōx Radio Button Group (fixed choice) + Ŕàďĩō ßũţţōń Ĝŕōũƥ (ƒĩxēď ćĥōĩćē) Dropdown (fixed choice) + Ďŕōƥďōŵń (ƒĩxēď ćĥōĩćē) Date + Ďàţē Date Time + Ďàţē Ţĩmē File + Ƒĩĺē Separator: Static Separator Line + Śēƥàŕàţōŕ: Śţàţĩć Śēƥàŕàţōŕ Ĺĩńē Hidden: Hidden field, can be used to insert data into form. + Ĥĩďďēń: Ĥĩďďēń ƒĩēĺď, ćàń ƀē ũśēď ţō ĩńśēŕţ ďàţà ĩńţō ƒōŕm. Static: Static value, displayed as-is. + Śţàţĩć: Śţàţĩć vàĺũē, ďĩśƥĺàŷēď àś-ĩś. authentik: Locale: Displays a list of locales authentik supports. + àũţĥēńţĩķ: Ĺōćàĺē: Ďĩśƥĺàŷś à ĺĩśţ ōƒ ĺōćàĺēś àũţĥēńţĩķ śũƥƥōŕţś. Preview errors + Ƥŕēvĩēŵ ēŕŕōŕś Data preview + Ďàţà ƥŕēvĩēŵ Unique name of this field, used for selecting fields in prompt stages. + Ũńĩǫũē ńàmē ōƒ ţĥĩś ƒĩēĺď, ũśēď ƒōŕ śēĺēćţĩńĝ ƒĩēĺďś ĩń ƥŕōmƥţ śţàĝēś. Field Key + Ƒĩēĺď Ķēŷ Name of the form field, also used to store the value. + Ńàmē ōƒ ţĥē ƒōŕm ƒĩēĺď, àĺśō ũśēď ţō śţōŕē ţĥē vàĺũē. When used in conjunction with a User Write stage, use attributes.foo to write attributes. + Ŵĥēń ũśēď ĩń ćōńĴũńćţĩōń ŵĩţĥ à Ũśēŕ Ŵŕĩţē śţàĝē, ũśē àţţŕĩƀũţēś.ƒōō ţō ŵŕĩţē àţţŕĩƀũţēś. Label + Ĺàƀēĺ Label shown next to/above the prompt. + Ĺàƀēĺ śĥōŵń ńēxţ ţō/àƀōvē ţĥē ƥŕōmƥţ. Required + Ŕēǫũĩŕēď Interpret placeholder as expression + Ĩńţēŕƥŕēţ ƥĺàćēĥōĺďēŕ àś ēxƥŕēśśĩōń When checked, the placeholder will be evaluated in the same way a property mapping is. If the evaluation fails, the placeholder itself is returned. + Ŵĥēń ćĥēćķēď, ţĥē ƥĺàćēĥōĺďēŕ ŵĩĺĺ ƀē ēvàĺũàţēď ĩń ţĥē śàmē ŵàŷ à ƥŕōƥēŕţŷ màƥƥĩńĝ ĩś. + Ĩƒ ţĥē ēvàĺũàţĩōń ƒàĩĺś, ţĥē ƥĺàćēĥōĺďēŕ ĩţśēĺƒ ĩś ŕēţũŕńēď. Placeholder + Ƥĺàćēĥōĺďēŕ Optionally provide a short hint that describes the expected input value. When creating a fixed choice field, enable interpreting as expression and return a list to return multiple choices. + Ōƥţĩōńàĺĺŷ ƥŕōvĩďē à śĥōŕţ ĥĩńţ ţĥàţ ďēśćŕĩƀēś ţĥē ēxƥēćţēď ĩńƥũţ vàĺũē. + Ŵĥēń ćŕēàţĩńĝ à ƒĩxēď ćĥōĩćē ƒĩēĺď, ēńàƀĺē ĩńţēŕƥŕēţĩńĝ àś ēxƥŕēśśĩōń àńď ŕēţũŕń à + ĺĩśţ ţō ŕēţũŕń mũĺţĩƥĺē ćĥōĩćēś. Interpret initial value as expression + Ĩńţēŕƥŕēţ ĩńĩţĩàĺ vàĺũē àś ēxƥŕēśśĩōń When checked, the initial value will be evaluated in the same way a property mapping is. If the evaluation fails, the initial value itself is returned. + Ŵĥēń ćĥēćķēď, ţĥē ĩńĩţĩàĺ vàĺũē ŵĩĺĺ ƀē ēvàĺũàţēď ĩń ţĥē śàmē ŵàŷ à ƥŕōƥēŕţŷ màƥƥĩńĝ ĩś. + Ĩƒ ţĥē ēvàĺũàţĩōń ƒàĩĺś, ţĥē ĩńĩţĩàĺ vàĺũē ĩţśēĺƒ ĩś ŕēţũŕńēď. Initial value + Ĩńĩţĩàĺ vàĺũē Optionally pre-fill the input with an initial value. When creating a fixed choice field, enable interpreting as expression and return a list to return multiple default choices. + Ōƥţĩōńàĺĺŷ ƥŕē-ƒĩĺĺ ţĥē ĩńƥũţ ŵĩţĥ àń ĩńĩţĩàĺ vàĺũē. + Ŵĥēń ćŕēàţĩńĝ à ƒĩxēď ćĥōĩćē ƒĩēĺď, ēńàƀĺē ĩńţēŕƥŕēţĩńĝ àś ēxƥŕēśśĩōń àńď + ŕēţũŕń à ĺĩśţ ţō ŕēţũŕń mũĺţĩƥĺē ďēƒàũĺţ ćĥōĩćēś. Help text + Ĥēĺƥ ţēxţ Any HTML can be used. + Àńŷ ĤŢMĹ ćàń ƀē ũśēď. Prompts + Ƥŕōmƥţś Single Prompts that can be used for Prompt Stages. + Śĩńĝĺē Ƥŕōmƥţś ţĥàţ ćàń ƀē ũśēď ƒōŕ Ƥŕōmƥţ Śţàĝēś. Field + Ƒĩēĺď Stages + Śţàĝēś Prompt(s) + Ƥŕōmƥţ(ś) Update Prompt + Ũƥďàţē Ƥŕōmƥţ Create Prompt + Ćŕēàţē Ƥŕōmƥţ Target + Ţàŕĝēţ Stage + Śţàĝē Evaluate when flow is planned + Ēvàĺũàţē ŵĥēń ƒĺōŵ ĩś ƥĺàńńēď Evaluate policies during the Flow planning process. + Ēvàĺũàţē ƥōĺĩćĩēś ďũŕĩńĝ ţĥē Ƒĺōŵ ƥĺàńńĩńĝ ƥŕōćēśś. Evaluate when stage is run + Ēvàĺũàţē ŵĥēń śţàĝē ĩś ŕũń Evaluate policies before the Stage is present to the user. + Ēvàĺũàţē ƥōĺĩćĩēś ƀēƒōŕē ţĥē Śţàĝē ĩś ƥŕēśēńţ ţō ţĥē ũśēŕ. Invalid response behavior + Ĩńvàĺĩď ŕēśƥōńśē ƀēĥàvĩōŕ Returns the error message and a similar challenge to the executor + Ŕēţũŕńś ţĥē ēŕŕōŕ mēśśàĝē àńď à śĩmĩĺàŕ ćĥàĺĺēńĝē ţō ţĥē ēxēćũţōŕ Restarts the flow from the beginning + Ŕēśţàŕţś ţĥē ƒĺōŵ ƒŕōm ţĥē ƀēĝĩńńĩńĝ Restarts the flow from the beginning, while keeping the flow context + Ŕēśţàŕţś ţĥē ƒĺōŵ ƒŕōm ţĥē ƀēĝĩńńĩńĝ, ŵĥĩĺē ķēēƥĩńĝ ţĥē ƒĺōŵ ćōńţēxţ Configure how the flow executor should handle an invalid response to a challenge given by this bound stage. + Ćōńƒĩĝũŕē ĥōŵ ţĥē ƒĺōŵ ēxēćũţōŕ śĥōũĺď ĥàńďĺē àń ĩńvàĺĩď ŕēśƥōńśē ţō à ćĥàĺĺēńĝē ĝĩvēń ƀŷ ţĥĩś ƀōũńď śţàĝē. Successfully updated stage. + Śũććēśśƒũĺĺŷ ũƥďàţēď śţàĝē. Successfully created stage. + Śũććēśśƒũĺĺŷ ćŕēàţēď śţàĝē. Stage used to configure a duo-based authenticator. This stage should be used for configuration flows. + Śţàĝē ũśēď ţō ćōńƒĩĝũŕē à ďũō-ƀàśēď àũţĥēńţĩćàţōŕ. Ţĥĩś śţàĝē śĥōũĺď ƀē ũśēď ƒōŕ ćōńƒĩĝũŕàţĩōń ƒĺōŵś. Authenticator type name + Àũţĥēńţĩćàţōŕ ţŷƥē ńàmē Display name of this authenticator, used by users when they enroll an authenticator. + Ďĩśƥĺàŷ ńàmē ōƒ ţĥĩś àũţĥēńţĩćàţōŕ, ũśēď ƀŷ ũśēŕś ŵĥēń ţĥēŷ ēńŕōĺĺ àń àũţĥēńţĩćàţōŕ. API Hostname + ÀƤĨ Ĥōśţńàmē Duo Auth API + Ďũō Àũţĥ ÀƤĨ Integration key + Ĩńţēĝŕàţĩōń ķēŷ Secret key + Śēćŕēţ ķēŷ Duo Admin API (optional) + Ďũō Àďmĩń ÀƤĨ (ōƥţĩōńàĺ) When using a Duo MFA, Access or Beyond plan, an Admin API application can be created. This will allow authentik to import devices automatically. + Ŵĥēń ũśĩńĝ à Ďũō MƑÀ, Àććēśś ōŕ ßēŷōńď ƥĺàń, àń Àďmĩń ÀƤĨ àƥƥĺĩćàţĩōń ćàń ƀē ćŕēàţēď. + Ţĥĩś ŵĩĺĺ àĺĺōŵ àũţĥēńţĩķ ţō ĩmƥōŕţ ďēvĩćēś àũţōmàţĩćàĺĺŷ. Stage-specific settings + Śţàĝē-śƥēćĩƒĩć śēţţĩńĝś Configuration flow + Ćōńƒĩĝũŕàţĩōń ƒĺōŵ Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage. + Ƒĺōŵ ũśēď ƀŷ àń àũţĥēńţĩćàţēď ũśēŕ ţō ćōńƒĩĝũŕē ţĥĩś Śţàĝē. Ĩƒ ēmƥţŷ, ũśēŕ ŵĩĺĺ ńōţ ƀē àƀĺē ţō ćōńƒĩĝũŕē ţĥĩś śţàĝē. Twilio Account SID + Ţŵĩĺĩō Àććōũńţ ŚĨĎ Get this value from https://console.twilio.com + Ĝēţ ţĥĩś vàĺũē ƒŕōm ĥţţƥś://ćōńśōĺē.ţŵĩĺĩō.ćōm Twilio Auth Token + Ţŵĩĺĩō Àũţĥ Ţōķēń Authentication Type + Àũţĥēńţĩćàţĩōń Ţŷƥē Basic Auth + ßàśĩć Àũţĥ Bearer Token + ßēàŕēŕ Ţōķēń External API URL + Ēxţēŕńàĺ ÀƤĨ ŨŔĹ This is the full endpoint to send POST requests to. + Ţĥĩś ĩś ţĥē ƒũĺĺ ēńďƥōĩńţ ţō śēńď ƤŌŚŢ ŕēǫũēśţś ţō. API Auth Username + ÀƤĨ Àũţĥ Ũśēŕńàmē This is the username to be used with basic auth or the token when used with bearer token + Ţĥĩś ĩś ţĥē ũśēŕńàmē ţō ƀē ũśēď ŵĩţĥ ƀàśĩć àũţĥ ōŕ ţĥē ţōķēń ŵĥēń ũśēď ŵĩţĥ ƀēàŕēŕ ţōķēń API Auth password + ÀƤĨ Àũţĥ ƥàśśŵōŕď This is the password to be used with basic auth + Ţĥĩś ĩś ţĥē ƥàśśŵōŕď ţō ƀē ũśēď ŵĩţĥ ƀàśĩć àũţĥ Mapping + Màƥƥĩńĝ Modify the payload sent to the custom provider. + Mōďĩƒŷ ţĥē ƥàŷĺōàď śēńţ ţō ţĥē ćũśţōm ƥŕōvĩďēŕ. Stage used to configure an SMS-based TOTP authenticator. + Śţàĝē ũśēď ţō ćōńƒĩĝũŕē àń ŚMŚ-ƀàśēď ŢŌŢƤ àũţĥēńţĩćàţōŕ. Twilio + Ţŵĩĺĩō Generic + Ĝēńēŕĩć From number + Ƒŕōm ńũmƀēŕ Number the SMS will be sent from. + Ńũmƀēŕ ţĥē ŚMŚ ŵĩĺĺ ƀē śēńţ ƒŕōm. Hash phone number + Ĥàśĥ ƥĥōńē ńũmƀēŕ If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons. Devices created from a stage with this enabled cannot be used with the authenticator validation stage. + Ĩƒ ēńàƀĺēď, ōńĺŷ à ĥàśĥ ōƒ ţĥē ƥĥōńē ńũmƀēŕ ŵĩĺĺ ƀē śàvēď. Ţĥĩś ćàń ƀē ďōńē ƒōŕ ďàţà-ƥŕōţēćţĩōń ŕēàśōńś. Ďēvĩćēś ćŕēàţēď ƒŕōm à śţàĝē ŵĩţĥ ţĥĩś ēńàƀĺēď ćàńńōţ ƀē ũśēď ŵĩţĥ ţĥē àũţĥēńţĩćàţōŕ vàĺĩďàţĩōń śţàĝē. Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows. + Śţàĝē ũśēď ţō ćōńƒĩĝũŕē à śţàţĩć àũţĥēńţĩćàţōŕ (ĩ.ē. śţàţĩć ţōķēńś). Ţĥĩś śţàĝē śĥōũĺď ƀē ũśēď ƒōŕ ćōńƒĩĝũŕàţĩōń ƒĺōŵś. Token count + Ţōķēń ćōũńţ Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator). + Śţàĝē ũśēď ţō ćōńƒĩĝũŕē à ŢŌŢƤ àũţĥēńţĩćàţōŕ (ĩ.ē. Àũţĥŷ/Ĝōōĝĺē Àũţĥēńţĩćàţōŕ). Digits + Ďĩĝĩţś 6 digits, widely compatible + 6 ďĩĝĩţś, ŵĩďēĺŷ ćōmƥàţĩƀĺē 8 digits, not compatible with apps like Google Authenticator + 8 ďĩĝĩţś, ńōţ ćōmƥàţĩƀĺē ŵĩţĥ àƥƥś ĺĩķē Ĝōōĝĺē Àũţĥēńţĩćàţōŕ Stage used to validate any authenticator. This stage should be used during authentication or authorization flows. + Śţàĝē ũśēď ţō vàĺĩďàţē àńŷ àũţĥēńţĩćàţōŕ. Ţĥĩś śţàĝē śĥōũĺď ƀē ũśēď ďũŕĩńĝ àũţĥēńţĩćàţĩōń ōŕ àũţĥōŕĩźàţĩōń ƒĺōŵś. Device classes + Ďēvĩćē ćĺàśśēś Static Tokens + Śţàţĩć Ţōķēńś TOTP Authenticators + ŢŌŢƤ Àũţĥēńţĩćàţōŕś WebAuthn Authenticators + ŴēƀÀũţĥń Àũţĥēńţĩćàţōŕś Duo Authenticators + Ďũō Àũţĥēńţĩćàţōŕś SMS-based Authenticators + ŚMŚ-ƀàśēď Àũţĥēńţĩćàţōŕś Device classes which can be used to authenticate. + Ďēvĩćē ćĺàśśēś ŵĥĩćĥ ćàń ƀē ũśēď ţō àũţĥēńţĩćàţē. Last validation threshold + Ĺàśţ vàĺĩďàţĩōń ţĥŕēśĥōĺď If any of the devices user of the types selected above have been used within this duration, this stage will be skipped. + Ĩƒ àńŷ ōƒ ţĥē ďēvĩćēś ũśēŕ ōƒ ţĥē ţŷƥēś śēĺēćţēď àƀōvē ĥàvē ƀēēń ũśēď ŵĩţĥĩń ţĥĩś ďũŕàţĩōń, ţĥĩś śţàĝē ŵĩĺĺ ƀē śķĩƥƥēď. Not configured action + Ńōţ ćōńƒĩĝũŕēď àćţĩōń Force the user to configure an authenticator + Ƒōŕćē ţĥē ũśēŕ ţō ćōńƒĩĝũŕē àń àũţĥēńţĩćàţōŕ Deny the user access + Ďēńŷ ţĥē ũśēŕ àććēśś WebAuthn User verification + ŴēƀÀũţĥń Ũśēŕ vēŕĩƒĩćàţĩōń User verification must occur. + Ũśēŕ vēŕĩƒĩćàţĩōń mũśţ ōććũŕ. User verification is preferred if available, but not required. + Ũśēŕ vēŕĩƒĩćàţĩōń ĩś ƥŕēƒēŕŕēď ĩƒ àvàĩĺàƀĺē, ƀũţ ńōţ ŕēǫũĩŕēď. User verification should not occur. + Ũśēŕ vēŕĩƒĩćàţĩōń śĥōũĺď ńōţ ōććũŕ. Configuration stages + Ćōńƒĩĝũŕàţĩōń śţàĝēś Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again. + Śţàĝēś ũśēď ţō ćōńƒĩĝũŕē Àũţĥēńţĩćàţōŕ ŵĥēń ũśēŕ ďōēśń'ţ ĥàvē àńŷ ćōmƥàţĩƀĺē ďēvĩćēś. Àƒţēŕ ţĥĩś ćōńƒĩĝũŕàţĩōń Śţàĝē ƥàśśēś, ţĥē ũśēŕ ĩś ńōţ ƥŕōmƥţēď àĝàĩń. When multiple stages are selected, the user can choose which one they want to enroll. + Ŵĥēń mũĺţĩƥĺē śţàĝēś àŕē śēĺēćţēď, ţĥē ũśēŕ ćàń ćĥōōśē ŵĥĩćĥ ōńē ţĥēŷ ŵàńţ ţō ēńŕōĺĺ. User verification + Ũśēŕ vēŕĩƒĩćàţĩōń Resident key requirement + Ŕēśĩďēńţ ķēŷ ŕēǫũĩŕēmēńţ Authenticator Attachment + Àũţĥēńţĩćàţōŕ Àţţàćĥmēńţ No preference is sent + Ńō ƥŕēƒēŕēńćē ĩś śēńţ A non-removable authenticator, like TouchID or Windows Hello + À ńōń-ŕēmōvàƀĺē àũţĥēńţĩćàţōŕ, ĺĩķē ŢōũćĥĨĎ ōŕ Ŵĩńďōŵś Ĥēĺĺō A "roaming" authenticator, like a YubiKey + À "ŕōàmĩńĝ" àũţĥēńţĩćàţōŕ, ĺĩķē à ŶũƀĩĶēŷ This stage checks the user's current session against the Google reCaptcha (or compatible) service. + Ţĥĩś śţàĝē ćĥēćķś ţĥē ũśēŕ'ś ćũŕŕēńţ śēśśĩōń àĝàĩńśţ ţĥē Ĝōōĝĺē ŕēĆàƥţćĥà (ōŕ ćōmƥàţĩƀĺē) śēŕvĩćē. Public Key + Ƥũƀĺĩć Ķēŷ Public key, acquired from https://www.google.com/recaptcha/intro/v3.html. + Ƥũƀĺĩć ķēŷ, àćǫũĩŕēď ƒŕōm ĥţţƥś://ŵŵŵ.ĝōōĝĺē.ćōm/ŕēćàƥţćĥà/ĩńţŕō/v3.ĥţmĺ. Private Key + Ƥŕĩvàţē Ķēŷ Private key, acquired from https://www.google.com/recaptcha/intro/v3.html. + Ƥŕĩvàţē ķēŷ, àćǫũĩŕēď ƒŕōm ĥţţƥś://ŵŵŵ.ĝōōĝĺē.ćōm/ŕēćàƥţćĥà/ĩńţŕō/v3.ĥţmĺ. Advanced settings + Àďvàńćēď śēţţĩńĝś JS URL + ĵŚ ŨŔĹ URL to fetch JavaScript from, defaults to recaptcha. Can be replaced with any compatible alternative. + ŨŔĹ ţō ƒēţćĥ ĵàvàŚćŕĩƥţ ƒŕōm, ďēƒàũĺţś ţō ŕēćàƥţćĥà. Ćàń ƀē ŕēƥĺàćēď ŵĩţĥ àńŷ ćōmƥàţĩƀĺē àĺţēŕńàţĩvē. API URL + ÀƤĨ ŨŔĹ URL used to validate captcha response, defaults to recaptcha. Can be replaced with any compatible alternative. + ŨŔĹ ũśēď ţō vàĺĩďàţē ćàƥţćĥà ŕēśƥōńśē, ďēƒàũĺţś ţō ŕēćàƥţćĥà. Ćàń ƀē ŕēƥĺàćēď ŵĩţĥ àńŷ ćōmƥàţĩƀĺē àĺţēŕńàţĩvē. Prompt for the user's consent. The consent can either be permanent or expire in a defined amount of time. + Ƥŕōmƥţ ƒōŕ ţĥē ũśēŕ'ś ćōńśēńţ. Ţĥē ćōńśēńţ ćàń ēĩţĥēŕ ƀē ƥēŕmàńēńţ ōŕ ēxƥĩŕē ĩń à ďēƒĩńēď àmōũńţ ōƒ ţĩmē. Always require consent + Àĺŵàŷś ŕēǫũĩŕē ćōńśēńţ Consent given last indefinitely + Ćōńśēńţ ĝĩvēń ĺàśţ ĩńďēƒĩńĩţēĺŷ Consent expires. + Ćōńśēńţ ēxƥĩŕēś. Consent expires in + Ćōńśēńţ ēxƥĩŕēś ĩń Offset after which consent expires. + Ōƒƒśēţ àƒţēŕ ŵĥĩćĥ ćōńśēńţ ēxƥĩŕēś. Dummy stage used for testing. Shows a simple continue button and always passes. + Ďũmmŷ śţàĝē ũśēď ƒōŕ ţēśţĩńĝ. Śĥōŵś à śĩmƥĺē ćōńţĩńũē ƀũţţōń àńď àĺŵàŷś ƥàśśēś. Throw error? + Ţĥŕōŵ ēŕŕōŕ? SMTP Host + ŚMŢƤ Ĥōśţ SMTP Port + ŚMŢƤ Ƥōŕţ SMTP Username + ŚMŢƤ Ũśēŕńàmē SMTP Password + ŚMŢƤ Ƥàśśŵōŕď Use TLS + Ũśē ŢĹŚ Use SSL + Ũśē ŚŚĹ From address + Ƒŕōm àďďŕēśś Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity. + Vēŕĩƒŷ ţĥē ũśēŕ'ś ēmàĩĺ àďďŕēśś ƀŷ śēńďĩńĝ ţĥēm à ōńē-ţĩmē-ĺĩńķ. Ćàń àĺśō ƀē ũśēď ƒōŕ ŕēćōvēŕŷ ţō vēŕĩƒŷ ţĥē ũśēŕ'ś àũţĥēńţĩćĩţŷ. Activate pending user on success + Àćţĩvàţē ƥēńďĩńĝ ũśēŕ ōń śũććēśś When a user returns from the email successfully, their account will be activated. + Ŵĥēń à ũśēŕ ŕēţũŕńś ƒŕōm ţĥē ēmàĩĺ śũććēśśƒũĺĺŷ, ţĥēĩŕ àććōũńţ ŵĩĺĺ ƀē àćţĩvàţēď. Use global settings + Ũśē ĝĺōƀàĺ śēţţĩńĝś When enabled, global Email connection settings will be used and connection settings below will be ignored. + Ŵĥēń ēńàƀĺēď, ĝĺōƀàĺ Ēmàĩĺ ćōńńēćţĩōń śēţţĩńĝś ŵĩĺĺ ƀē ũśēď àńď ćōńńēćţĩōń śēţţĩńĝś ƀēĺōŵ ŵĩĺĺ ƀē ĩĝńōŕēď. Token expiry + Ţōķēń ēxƥĩŕŷ Time in minutes the token sent is valid. + Ţĩmē ĩń mĩńũţēś ţĥē ţōķēń śēńţ ĩś vàĺĩď. Template + Ţēmƥĺàţē Let the user identify themselves with their username or Email address. + Ĺēţ ţĥē ũśēŕ ĩďēńţĩƒŷ ţĥēmśēĺvēś ŵĩţĥ ţĥēĩŕ ũśēŕńàmē ōŕ Ēmàĩĺ àďďŕēśś. User fields + Ũśēŕ ƒĩēĺďś UPN + ŨƤŃ Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources. + Ƒĩēĺďś à ũśēŕ ćàń ĩďēńţĩƒŷ ţĥēmśēĺvēś ŵĩţĥ. Ĩƒ ńō ƒĩēĺďś àŕē śēĺēćţēď, ţĥē ũśēŕ ŵĩĺĺ ōńĺŷ ƀē àƀĺē ţō ũśē śōũŕćēś. Password stage + Ƥàśśŵōŕď śţàĝē When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks. + Ŵĥēń śēĺēćţēď, à ƥàśśŵōŕď ƒĩēĺď ĩś śĥōŵń ōń ţĥē śàmē ƥàĝē ĩńśţēàď ōƒ à śēƥàŕàţē ƥàĝē. Ţĥĩś ƥŕēvēńţś ũśēŕńàmē ēńũmēŕàţĩōń àţţàćķś. Case insensitive matching + Ćàśē ĩńśēńśĩţĩvē màţćĥĩńĝ When enabled, user fields are matched regardless of their casing. + Ŵĥēń ēńàƀĺēď, ũśēŕ ƒĩēĺďś àŕē màţćĥēď ŕēĝàŕďĺēśś ōƒ ţĥēĩŕ ćàśĩńĝ. Show matched user + Śĥōŵ màţćĥēď ũśēŕ When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown. + Ŵĥēń à vàĺĩď ũśēŕńàmē/ēmàĩĺ ĥàś ƀēēń ēńţēŕēď, àńď ţĥĩś ōƥţĩōń ĩś ēńàƀĺēď, ţĥē ũśēŕ'ś ũśēŕńàmē àńď àvàţàŕ ŵĩĺĺ ƀē śĥōŵń. Ōţĥēŕŵĩśē, ţĥē ţēxţ ţĥàţ ţĥē ũśēŕ ēńţēŕēď ŵĩĺĺ ƀē śĥōŵń. Source settings + Śōũŕćē śēţţĩńĝś Sources + Śōũŕćēś Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP. + Śēĺēćţ śōũŕćēś śĥōũĺď ƀē śĥōŵń ƒōŕ ũśēŕś ţō àũţĥēńţĩćàţē ŵĩţĥ. Ţĥĩś ōńĺŷ àƒƒēćţś ŵēƀ-ƀàśēď śōũŕćēś, ńōţ ĹĎÀƤ. Show sources' labels + Śĥōŵ śōũŕćēś' ĺàƀēĺś By default, only icons are shown for sources. Enable this to show their full names. + ßŷ ďēƒàũĺţ, ōńĺŷ ĩćōńś àŕē śĥōŵń ƒōŕ śōũŕćēś. Ēńàƀĺē ţĥĩś ţō śĥōŵ ţĥēĩŕ ƒũĺĺ ńàmēś. Passwordless flow + Ƥàśśŵōŕďĺēśś ƒĺōŵ Optional passwordless flow, which is linked at the bottom of the page. When configured, users can use this flow to authenticate with a WebAuthn authenticator, without entering any details. + Ōƥţĩōńàĺ ƥàśśŵōŕďĺēśś ƒĺōŵ, ŵĥĩćĥ ĩś ĺĩńķēď àţ ţĥē ƀōţţōm ōƒ ţĥē ƥàĝē. Ŵĥēń ćōńƒĩĝũŕēď, ũśēŕś ćàń ũśē ţĥĩś ƒĺōŵ ţō àũţĥēńţĩćàţē ŵĩţĥ à ŴēƀÀũţĥń àũţĥēńţĩćàţōŕ, ŵĩţĥōũţ ēńţēŕĩńĝ àńŷ ďēţàĩĺś. Optional enrollment flow, which is linked at the bottom of the page. + Ōƥţĩōńàĺ ēńŕōĺĺmēńţ ƒĺōŵ, ŵĥĩćĥ ĩś ĺĩńķēď àţ ţĥē ƀōţţōm ōƒ ţĥē ƥàĝē. Optional recovery flow, which is linked at the bottom of the page. + Ōƥţĩōńàĺ ŕēćōvēŕŷ ƒĺōŵ, ŵĥĩćĥ ĩś ĺĩńķēď àţ ţĥē ƀōţţōm ōƒ ţĥē ƥàĝē. This stage can be included in enrollment flows to accept invitations. + Ţĥĩś śţàĝē ćàń ƀē ĩńćĺũďēď ĩń ēńŕōĺĺmēńţ ƒĺōŵś ţō àććēƥţ ĩńvĩţàţĩōńś. Continue flow without invitation + Ćōńţĩńũē ƒĺōŵ ŵĩţĥōũţ ĩńvĩţàţĩōń If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given. + Ĩƒ ţĥĩś ƒĺàĝ ĩś śēţ, ţĥĩś Śţàĝē ŵĩĺĺ Ĵũmƥ ţō ţĥē ńēxţ Śţàĝē ŵĥēń ńō Ĩńvĩţàţĩōń ĩś ĝĩvēń. ßŷ ďēƒàũĺţ ţĥĩś Śţàĝē ŵĩĺĺ ćàńćēĺ ţĥē Ƒĺōŵ ŵĥēń ńō ĩńvĩţàţĩōń ĩś ĝĩvēń. Validate the user's password against the selected backend(s). + Vàĺĩďàţē ţĥē ũśēŕ'ś ƥàśśŵōŕď àĝàĩńśţ ţĥē śēĺēćţēď ƀàćķēńď(ś). Backends + ßàćķēńďś User database + standard password + Ũśēŕ ďàţàƀàśē + śţàńďàŕď ƥàśśŵōŕď User database + app passwords + Ũśēŕ ďàţàƀàśē + àƥƥ ƥàśśŵōŕďś User database + LDAP password + Ũśēŕ ďàţàƀàśē + ĹĎÀƤ ƥàśśŵōŕď Selection of backends to test the password against. + Śēĺēćţĩōń ōƒ ƀàćķēńďś ţō ţēśţ ţĥē ƥàśśŵōŕď àĝàĩńśţ. Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password. + Ƒĺōŵ ũśēď ƀŷ àń àũţĥēńţĩćàţēď ũśēŕ ţō ćōńƒĩĝũŕē ţĥēĩŕ ƥàśśŵōŕď. Ĩƒ ēmƥţŷ, ũśēŕ ŵĩĺĺ ńōţ ƀē àƀĺē ţō ćōńƒĩĝũŕē ćĥàńĝē ţĥēĩŕ ƥàśśŵōŕď. Failed attempts before cancel + Ƒàĩĺēď àţţēmƥţś ƀēƒōŕē ćàńćēĺ How many attempts a user has before the flow is canceled. To lock the user out, use a reputation policy and a user_write stage. + Ĥōŵ màńŷ àţţēmƥţś à ũśēŕ ĥàś ƀēƒōŕē ţĥē ƒĺōŵ ĩś ćàńćēĺēď. Ţō ĺōćķ ţĥē ũśēŕ ōũţ, ũśē à ŕēƥũţàţĩōń ƥōĺĩćŷ àńď à ũśēŕ_ŵŕĩţē śţàĝē. Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable. + Śĥōŵ àŕƀĩţŕàŕŷ ĩńƥũţ ƒĩēĺďś ţō ţĥē ũśēŕ, ƒōŕ ēxàmƥĺē ďũŕĩńĝ ēńŕōĺĺmēńţ. Ďàţà ĩś śàvēď ĩń ţĥē ƒĺōŵ ćōńţēxţ ũńďēŕ ţĥē 'ƥŕōmƥţ_ďàţà' vàŕĩàƀĺē. Fields + Ƒĩēĺďś ("", of type ) + ("", ōƒ ţŷƥē ) Validation Policies + Vàĺĩďàţĩōń Ƥōĺĩćĩēś Selected policies are executed when the stage is submitted to validate the data. + Śēĺēćţēď ƥōĺĩćĩēś àŕē ēxēćũţēď ŵĥēń ţĥē śţàĝē ĩś śũƀmĩţţēď ţō vàĺĩďàţē ţĥē ďàţà. Delete the currently pending user. CAUTION, this stage does not ask for confirmation. Use a consent stage to ensure the user is aware of their actions. + Ďēĺēţē ţĥē ćũŕŕēńţĺŷ ƥēńďĩńĝ ũśēŕ. ĆÀŨŢĨŌŃ, ţĥĩś śţàĝē ďōēś ńōţ àśķ ƒōŕ ćōńƒĩŕmàţĩōń. Ũśē à ćōńśēńţ śţàĝē ţō ēńśũŕē ţĥē ũśēŕ ĩś àŵàŕē ōƒ ţĥēĩŕ àćţĩōńś. Log the currently pending user in. + Ĺōĝ ţĥē ćũŕŕēńţĺŷ ƥēńďĩńĝ ũśēŕ ĩń. Session duration + Śēśśĩōń ďũŕàţĩōń Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed. + Ďēţēŕmĩńēś ĥōŵ ĺōńĝ à śēśśĩōń ĺàśţś. Ďēƒàũĺţ ōƒ 0 śēćōńďś mēàńś ţĥàţ ţĥē śēśśĩōńś ĺàśţś ũńţĩĺ ţĥē ƀŕōŵśēŕ ĩś ćĺōśēď. Different browsers handle session cookies differently, and might not remove them even when the browser is closed. + Ďĩƒƒēŕēńţ ƀŕōŵśēŕś ĥàńďĺē śēśśĩōń ćōōķĩēś ďĩƒƒēŕēńţĺŷ, àńď mĩĝĥţ ńōţ ŕēmōvē ţĥēm ēvēń ŵĥēń ţĥē ƀŕōŵśēŕ ĩś ćĺōśēď. See here. + Śēē ĥēŕē. Stay signed in offset + Śţàŷ śĩĝńēď ĩń ōƒƒśēţ If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. + Ĩƒ śēţ ţō à ďũŕàţĩōń àƀōvē 0, ţĥē ũśēŕ ŵĩĺĺ ĥàvē ţĥē ōƥţĩōń ţō ćĥōōśē ţō "śţàŷ śĩĝńēď ĩń", ŵĥĩćĥ ŵĩĺĺ ēxţēńď ţĥēĩŕ śēśśĩōń ƀŷ ţĥē ţĩmē śƥēćĩƒĩēď ĥēŕē. Terminate other sessions + Ţēŕmĩńàţē ōţĥēŕ śēśśĩōńś When enabled, all previous sessions of the user will be terminated. + Ŵĥēń ēńàƀĺēď, àĺĺ ƥŕēvĩōũś śēśśĩōńś ōƒ ţĥē ũśēŕ ŵĩĺĺ ƀē ţēŕmĩńàţēď. Remove the user from the current session. + Ŕēmōvē ţĥē ũśēŕ ƒŕōm ţĥē ćũŕŕēńţ śēśśĩōń. Write any data from the flow's context's 'prompt_data' to the currently pending user. If no user is pending, a new user is created, and data is written to them. + Ŵŕĩţē àńŷ ďàţà ƒŕōm ţĥē ƒĺōŵ'ś ćōńţēxţ'ś 'ƥŕōmƥţ_ďàţà' ţō ţĥē ćũŕŕēńţĺŷ ƥēńďĩńĝ ũśēŕ. Ĩƒ ńō ũśēŕ + ĩś ƥēńďĩńĝ, à ńēŵ ũśēŕ ĩś ćŕēàţēď, àńď ďàţà ĩś ŵŕĩţţēń ţō ţĥēm. Never create users + Ńēvēŕ ćŕēàţē ũśēŕś When no user is present in the flow context, the stage will fail. + Ŵĥēń ńō ũśēŕ ĩś ƥŕēśēńţ ĩń ţĥē ƒĺōŵ ćōńţēxţ, ţĥē śţàĝē ŵĩĺĺ ƒàĩĺ. Create users when required + Ćŕēàţē ũśēŕś ŵĥēń ŕēǫũĩŕēď When no user is present in the the flow context, a new user is created. + Ŵĥēń ńō ũśēŕ ĩś ƥŕēśēńţ ĩń ţĥē ţĥē ƒĺōŵ ćōńţēxţ, à ńēŵ ũśēŕ ĩś ćŕēàţēď. Always create new users + Àĺŵàŷś ćŕēàţē ńēŵ ũśēŕś Create a new user even if a user is in the flow context. + Ćŕēàţē à ńēŵ ũśēŕ ēvēń ĩƒ à ũśēŕ ĩś ĩń ţĥē ƒĺōŵ ćōńţēxţ. Create users as inactive + Ćŕēàţē ũśēŕś àś ĩńàćţĩvē Mark newly created users as inactive. + Màŕķ ńēŵĺŷ ćŕēàţēď ũśēŕś àś ĩńàćţĩvē. User path template + Ũśēŕ ƥàţĥ ţēmƥĺàţē Path new users will be created under. If left blank, the default path will be used. + Ƥàţĥ ńēŵ ũśēŕś ŵĩĺĺ ƀē ćŕēàţēď ũńďēŕ. Ĩƒ ĺēƒţ ƀĺàńķ, ţĥē ďēƒàũĺţ ƥàţĥ ŵĩĺĺ ƀē ũśēď. Newly created users are added to this group, if a group is selected. + Ńēŵĺŷ ćŕēàţēď ũśēŕś àŕē àďďēď ţō ţĥĩś ĝŕōũƥ, ĩƒ à ĝŕōũƥ ĩś śēĺēćţēď. New stage + Ńēŵ śţàĝē Create a new stage. + Ćŕēàţē à ńēŵ śţàĝē. Successfully imported device. + Śũććēśśƒũĺĺŷ ĩmƥōŕţēď ďēvĩćē. The user in authentik this device will be assigned to. + Ţĥē ũśēŕ ĩń àũţĥēńţĩķ ţĥĩś ďēvĩćē ŵĩĺĺ ƀē àśśĩĝńēď ţō. Duo User ID + Ďũō Ũśēŕ ĨĎ The user ID in Duo, can be found in the URL after clicking on a user. + Ţĥē ũśēŕ ĨĎ ĩń Ďũō, ćàń ƀē ƒōũńď ĩń ţĥē ŨŔĹ àƒţēŕ ćĺĩćķĩńĝ ōń à ũśēŕ. Automatic import + Àũţōmàţĩć ĩmƥōŕţ Successfully imported devices. + Śũććēśśƒũĺĺŷ ĩmƥōŕţēď ďēvĩćēś. Start automatic import + Śţàŕţ àũţōmàţĩć ĩmƥōŕţ Or manually import + Ōŕ màńũàĺĺŷ ĩmƥōŕţ Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow. + Śţàĝēś àŕē śĩńĝĺē śţēƥś ōƒ à Ƒĺōŵ ţĥàţ à ũśēŕ ĩś ĝũĩďēď ţĥŕōũĝĥ. À śţàĝē ćàń ōńĺŷ ƀē ēxēćũţēď ƒŕōm ŵĩţĥĩń à ƒĺōŵ. Flows + Ƒĺōŵś Stage(s) + Śţàĝē(ś) Import + Ĩmƥōŕţ Import Duo device + Ĩmƥōŕţ Ďũō ďēvĩćē Successfully updated flow. + Śũććēśśƒũĺĺŷ ũƥďàţēď ƒĺōŵ. Successfully created flow. + Śũććēśśƒũĺĺŷ ćŕēàţēď ƒĺōŵ. Shown as the Title in Flow pages. + Śĥōŵń àś ţĥē Ţĩţĺē ĩń Ƒĺōŵ ƥàĝēś. Visible in the URL. + Vĩśĩƀĺē ĩń ţĥē ŨŔĹ. Designation + Ďēśĩĝńàţĩōń Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik. + Ďēćĩďēś ŵĥàţ ţĥĩś Ƒĺōŵ ĩś ũśēď ƒōŕ. Ƒōŕ ēxàmƥĺē, ţĥē Àũţĥēńţĩćàţĩōń ƒĺōŵ ĩś ŕēďĩŕēćţ ţō ŵĥēń àń ũń-àũţĥēńţĩćàţēď ũśēŕ vĩśĩţś àũţĥēńţĩķ. No requirement + Ńō ŕēǫũĩŕēmēńţ Require authentication + Ŕēǫũĩŕē àũţĥēńţĩćàţĩōń Require no authentication. + Ŕēǫũĩŕē ńō àũţĥēńţĩćàţĩōń. Require superuser. + Ŕēǫũĩŕē śũƥēŕũśēŕ. Required authentication level for this flow. + Ŕēǫũĩŕēď àũţĥēńţĩćàţĩōń ĺēvēĺ ƒōŕ ţĥĩś ƒĺōŵ. Behavior settings + ßēĥàvĩōŕ śēţţĩńĝś Compatibility mode + Ćōmƥàţĩƀĩĺĩţŷ mōďē Increases compatibility with password managers and mobile devices. + Ĩńćŕēàśēś ćōmƥàţĩƀĩĺĩţŷ ŵĩţĥ ƥàśśŵōŕď màńàĝēŕś àńď mōƀĩĺē ďēvĩćēś. Denied action + Ďēńĩēď àćţĩōń Will follow the ?next parameter if set, otherwise show a message + Ŵĩĺĺ ƒōĺĺōŵ ţĥē ?ńēxţ ƥàŕàmēţēŕ ĩƒ śēţ, ōţĥēŕŵĩśē śĥōŵ à mēśśàĝē Will either follow the ?next parameter or redirect to the default interface + Ŵĩĺĺ ēĩţĥēŕ ƒōĺĺōŵ ţĥē ?ńēxţ ƥàŕàmēţēŕ ōŕ ŕēďĩŕēćţ ţō ţĥē ďēƒàũĺţ ĩńţēŕƒàćē Will notify the user the flow isn't applicable + Ŵĩĺĺ ńōţĩƒŷ ţĥē ũśēŕ ţĥē ƒĺōŵ ĩśń'ţ àƥƥĺĩćàƀĺē Decides the response when a policy denies access to this flow for a user. + Ďēćĩďēś ţĥē ŕēśƥōńśē ŵĥēń à ƥōĺĩćŷ ďēńĩēś àććēśś ţō ţĥĩś ƒĺōŵ ƒōŕ à ũśēŕ. Appearance settings + Àƥƥēàŕàńćē śēţţĩńĝś Layout + Ĺàŷōũţ Background + ßàćķĝŕōũńď Background shown during execution. + ßàćķĝŕōũńď śĥōŵń ďũŕĩńĝ ēxēćũţĩōń. Clear background + Ćĺēàŕ ƀàćķĝŕōũńď Delete currently set background image. + Ďēĺēţē ćũŕŕēńţĺŷ śēţ ƀàćķĝŕōũńď ĩmàĝē. Successfully imported flow. + Śũććēśśƒũĺĺŷ ĩmƥōŕţēď ƒĺōŵ. .yaml files, which can be found on goauthentik.io and can be exported by authentik. + .ŷàmĺ ƒĩĺēś, ŵĥĩćĥ ćàń ƀē ƒōũńď ōń ĝōàũţĥēńţĩķ.ĩō àńď ćàń ƀē ēxƥōŕţēď ƀŷ àũţĥēńţĩķ. Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them. + Ƒĺōŵś ďēśćŕĩƀē à ćĥàĩń ōƒ Śţàĝēś ţō àũţĥēńţĩćàţē, ēńŕōĺĺ ōŕ ŕēćōvēŕ à ũśēŕ. Śţàĝēś àŕē ćĥōśēń ƀàśēď ōń ƥōĺĩćĩēś àƥƥĺĩēď ţō ţĥēm. Flow(s) + Ƒĺōŵ(ś) Update Flow + Ũƥďàţē Ƒĺōŵ Create Flow + Ćŕēàţē Ƒĺōŵ Import Flow + Ĩmƥōŕţ Ƒĺōŵ Successfully cleared flow cache + Śũććēśśƒũĺĺŷ ćĺēàŕēď ƒĺōŵ ćàćĥē Failed to delete flow cache + Ƒàĩĺēď ţō ďēĺēţē ƒĺōŵ ćàćĥē Clear Flow cache + Ćĺēàŕ Ƒĺōŵ ćàćĥē Are you sure you want to clear the flow cache? This will cause all flows to be re-evaluated on their next usage. + Àŕē ŷōũ śũŕē ŷōũ ŵàńţ ţō ćĺēàŕ ţĥē ƒĺōŵ ćàćĥē? + Ţĥĩś ŵĩĺĺ ćàũśē àĺĺ ƒĺōŵś ţō ƀē ŕē-ēvàĺũàţēď ōń ţĥēĩŕ ńēxţ ũśàĝē. Stage binding(s) + Śţàĝē ƀĩńďĩńĝ(ś) Stage type + Śţàĝē ţŷƥē Edit Stage + Ēďĩţ Śţàĝē Update Stage binding + Ũƥďàţē Śţàĝē ƀĩńďĩńĝ These bindings control if this stage will be applied to the flow. + Ţĥēśē ƀĩńďĩńĝś ćōńţŕōĺ ĩƒ ţĥĩś śţàĝē ŵĩĺĺ ƀē àƥƥĺĩēď ţō ţĥē ƒĺōŵ. No Stages bound + Ńō Śţàĝēś ƀōũńď No stages are currently bound to this flow. + Ńō śţàĝēś àŕē ćũŕŕēńţĺŷ ƀōũńď ţō ţĥĩś ƒĺōŵ. Create Stage binding + Ćŕēàţē Śţàĝē ƀĩńďĩńĝ Bind stage + ßĩńď śţàĝē Bind existing stage + ßĩńď ēxĩśţĩńĝ śţàĝē Flow Overview + Ƒĺōŵ Ōvēŕvĩēŵ Related actions + Ŕēĺàţēď àćţĩōńś Execute flow + Ēxēćũţē ƒĺōŵ Normal + Ńōŕmàĺ with current user + ŵĩţĥ ćũŕŕēńţ ũśēŕ with inspector + ŵĩţĥ ĩńśƥēćţōŕ Export flow + Ēxƥōŕţ ƒĺōŵ Export + Ēxƥōŕţ Stage Bindings + Śţàĝē ßĩńďĩńĝś These bindings control which users can access this flow. + Ţĥēśē ƀĩńďĩńĝś ćōńţŕōĺ ŵĥĩćĥ ũśēŕś ćàń àććēśś ţĥĩś ƒĺōŵ. Event Log + Ēvēńţ Ĺōĝ Event + Ēvēńţ Event info + Ēvēńţ ĩńƒō Created + Ćŕēàţēď Successfully updated transport. + Śũććēśśƒũĺĺŷ ũƥďàţēď ţŕàńśƥōŕţ. Successfully created transport. + Śũććēśśƒũĺĺŷ ćŕēàţēď ţŕàńśƥōŕţ. Local (notifications will be created within authentik) + Ĺōćàĺ (ńōţĩƒĩćàţĩōńś ŵĩĺĺ ƀē ćŕēàţēď ŵĩţĥĩń àũţĥēńţĩķ) Webhook (generic) + Ŵēƀĥōōķ (ĝēńēŕĩć) Webhook (Slack/Discord) + Ŵēƀĥōōķ (Śĺàćķ/Ďĩśćōŕď) Webhook URL + Ŵēƀĥōōķ ŨŔĹ Webhook Mapping + Ŵēƀĥōōķ Màƥƥĩńĝ Send once + Śēńď ōńćē Only send notification once, for example when sending a webhook into a chat channel. + Ōńĺŷ śēńď ńōţĩƒĩćàţĩōń ōńćē, ƒōŕ ēxàmƥĺē ŵĥēń śēńďĩńĝ à ŵēƀĥōōķ ĩńţō à ćĥàţ ćĥàńńēĺ. Notification Transports + Ńōţĩƒĩćàţĩōń Ţŕàńśƥōŕţś Define how notifications are sent to users, like Email or Webhook. + Ďēƒĩńē ĥōŵ ńōţĩƒĩćàţĩōńś àŕē śēńţ ţō ũśēŕś, ĺĩķē Ēmàĩĺ ōŕ Ŵēƀĥōōķ. Notification transport(s) + Ńōţĩƒĩćàţĩōń ţŕàńśƥōŕţ(ś) Update Notification Transport + Ũƥďàţē Ńōţĩƒĩćàţĩōń Ţŕàńśƥōŕţ Create Notification Transport + Ćŕēàţē Ńōţĩƒĩćàţĩōń Ţŕàńśƥōŕţ Successfully updated rule. + Śũććēśśƒũĺĺŷ ũƥďàţēď ŕũĺē. Successfully created rule. + Śũććēśśƒũĺĺŷ ćŕēàţēď ŕũĺē. Select the group of users which the alerts are sent to. If no group is selected the rule is disabled. + Śēĺēćţ ţĥē ĝŕōũƥ ōƒ ũśēŕś ŵĥĩćĥ ţĥē àĺēŕţś àŕē śēńţ ţō. Ĩƒ ńō ĝŕōũƥ ĩś śēĺēćţēď ţĥē ŕũĺē ĩś ďĩśàƀĺēď. Transports + Ţŕàńśƥōŕţś Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI. + Śēĺēćţ ŵĥĩćĥ ţŕàńśƥōŕţś śĥōũĺď ƀē ũśēď ţō ńōţĩƒŷ ţĥē ũśēŕ. Ĩƒ ńōńē àŕē śēĺēćţēď, ţĥē ńōţĩƒĩćàţĩōń ŵĩĺĺ ōńĺŷ ƀē śĥōŵń ĩń ţĥē àũţĥēńţĩķ ŨĨ. Severity + Śēvēŕĩţŷ Notification Rules + Ńōţĩƒĩćàţĩōń Ŕũĺēś Send notifications whenever a specific Event is created and matched by policies. + Śēńď ńōţĩƒĩćàţĩōńś ŵĥēńēvēŕ à śƥēćĩƒĩć Ēvēńţ ĩś ćŕēàţēď àńď màţćĥēď ƀŷ ƥōĺĩćĩēś. Sent to group + Śēńţ ţō ĝŕōũƥ Notification rule(s) + Ńōţĩƒĩćàţĩōń ŕũĺē(ś) None (rule disabled) + Ńōńē (ŕũĺē ďĩśàƀĺēď) Update Notification Rule + Ũƥďàţē Ńōţĩƒĩćàţĩōń Ŕũĺē Create Notification Rule + Ćŕēàţē Ńōţĩƒĩćàţĩōń Ŕũĺē These bindings control upon which events this rule triggers. Bindings to groups/users are checked against the user of the event. + Ţĥēśē ƀĩńďĩńĝś ćōńţŕōĺ ũƥōń ŵĥĩćĥ ēvēńţś ţĥĩś ŕũĺē ţŕĩĝĝēŕś. +ßĩńďĩńĝś ţō ĝŕōũƥś/ũśēŕś àŕē ćĥēćķēď àĝàĩńśţ ţĥē ũśēŕ ōƒ ţĥē ēvēńţ. Outpost Deployment Info + Ōũţƥōśţ Ďēƥĺōŷmēńţ Ĩńƒō View deployment documentation + Vĩēŵ ďēƥĺōŷmēńţ ďōćũmēńţàţĩōń Click to copy token + Ćĺĩćķ ţō ćōƥŷ ţōķēń If your authentik Instance is using a self-signed certificate, set this value. + Ĩƒ ŷōũŕ àũţĥēńţĩķ Ĩńśţàńćē ĩś ũśĩńĝ à śēĺƒ-śĩĝńēď ćēŕţĩƒĩćàţē, śēţ ţĥĩś vàĺũē. If your authentik_host setting does not match the URL you want to login with, add this setting. + Ĩƒ ŷōũŕ àũţĥēńţĩķ_ĥōśţ śēţţĩńĝ ďōēś ńōţ màţćĥ ţĥē ŨŔĹ ŷōũ ŵàńţ ţō ĺōĝĩń ŵĩţĥ, àďď ţĥĩś śēţţĩńĝ. Successfully updated outpost. + Śũććēśśƒũĺĺŷ ũƥďàţēď ōũţƥōśţ. Successfully created outpost. + Śũććēśśƒũĺĺŷ ćŕēàţēď ōũţƥōśţ. Radius + Ŕàďĩũś Integration + Ĩńţēĝŕàţĩōń Selecting an integration enables the management of the outpost by authentik. + Śēĺēćţĩńĝ àń ĩńţēĝŕàţĩōń ēńàƀĺēś ţĥē màńàĝēmēńţ ōƒ ţĥē ōũţƥōśţ ƀŷ àũţĥēńţĩķ. You can only select providers that match the type of the outpost. + Ŷōũ ćàń ōńĺŷ śēĺēćţ ƥŕōvĩďēŕś ţĥàţ màţćĥ ţĥē ţŷƥē ōƒ ţĥē ōũţƥōśţ. Configuration + Ćōńƒĩĝũŕàţĩōń See more here: + Śēē mōŕē ĥēŕē: Documentation + Ďōćũmēńţàţĩōń Last seen + Ĺàśţ śēēń , should be + , śĥōũĺď ƀē Hostname + Ĥōśţńàmē Not available + Ńōţ àvàĩĺàƀĺē Last seen: + Ĺàśţ śēēń: Unknown type + Ũńķńōŵń ţŷƥē Outposts + Ōũţƥōśţś Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies. + Ōũţƥōśţś àŕē ďēƥĺōŷmēńţś ōƒ àũţĥēńţĩķ ćōmƥōńēńţś ţō śũƥƥōŕţ ďĩƒƒēŕēńţ ēńvĩŕōńmēńţś àńď ƥŕōţōćōĺś, ĺĩķē ŕēvēŕśē ƥŕōxĩēś. Health and Version + Ĥēàĺţĥ àńď Vēŕśĩōń Warning: authentik Domain is not configured, authentication will not work. + Ŵàŕńĩńĝ: àũţĥēńţĩķ Ďōmàĩń ĩś ńōţ ćōńƒĩĝũŕēď, àũţĥēńţĩćàţĩōń ŵĩĺĺ ńōţ ŵōŕķ. Logging in via . + Ĺōĝĝĩńĝ ĩń vĩà . No integration active + Ńō ĩńţēĝŕàţĩōń àćţĩvē Update Outpost + Ũƥďàţē Ōũţƥōśţ View Deployment Info + Vĩēŵ Ďēƥĺōŷmēńţ Ĩńƒō Detailed health (one instance per column, data is cached so may be out of date) + Ďēţàĩĺēď ĥēàĺţĥ (ōńē ĩńśţàńćē ƥēŕ ćōĺũmń, ďàţà ĩś ćàćĥēď śō màŷ ƀē ōũţ ōƒ ďàţē) Outpost(s) + Ōũţƥōśţ(ś) Create Outpost + Ćŕēàţē Ōũţƥōśţ Successfully updated integration. + Śũććēśśƒũĺĺŷ ũƥďàţēď ĩńţēĝŕàţĩōń. Successfully created integration. + Śũććēśśƒũĺĺŷ ćŕēàţēď ĩńţēĝŕàţĩōń. Local + Ĺōćàĺ If enabled, use the local connection. Required Docker socket/Kubernetes Integration. + Ĩƒ ēńàƀĺēď, ũśē ţĥē ĺōćàĺ ćōńńēćţĩōń. Ŕēǫũĩŕēď Ďōćķēŕ śōćķēţ/Ķũƀēŕńēţēś Ĩńţēĝŕàţĩōń. Docker URL + Ďōćķēŕ ŨŔĹ Can be in the format of 'unix://' when connecting to a local docker daemon, using 'ssh://' to connect via SSH, or 'https://:2376' when connecting to a remote system. + Ćàń ƀē ĩń ţĥē ƒōŕmàţ ōƒ 'ũńĩx://' ŵĥēń ćōńńēćţĩńĝ ţō à ĺōćàĺ ďōćķēŕ ďàēmōń, ũśĩńĝ 'śśĥ://' ţō ćōńńēćţ vĩà ŚŚĤ, ōŕ 'ĥţţƥś://:2376' ŵĥēń ćōńńēćţĩńĝ ţō à ŕēmōţē śŷśţēm. CA which the endpoint's Certificate is verified against. Can be left empty for no validation. + ĆÀ ŵĥĩćĥ ţĥē ēńďƥōĩńţ'ś Ćēŕţĩƒĩćàţē ĩś vēŕĩƒĩēď àĝàĩńśţ. Ćàń ƀē ĺēƒţ ēmƥţŷ ƒōŕ ńō vàĺĩďàţĩōń. TLS Authentication Certificate/SSH Keypair + ŢĹŚ Àũţĥēńţĩćàţĩōń Ćēŕţĩƒĩćàţē/ŚŚĤ Ķēŷƥàĩŕ Certificate/Key used for authentication. Can be left empty for no authentication. + Ćēŕţĩƒĩćàţē/Ķēŷ ũśēď ƒōŕ àũţĥēńţĩćàţĩōń. Ćàń ƀē ĺēƒţ ēmƥţŷ ƒōŕ ńō àũţĥēńţĩćàţĩōń. When connecting via SSH, this keypair is used for authentication. + Ŵĥēń ćōńńēćţĩńĝ vĩà ŚŚĤ, ţĥĩś ķēŷƥàĩŕ ĩś ũśēď ƒōŕ àũţĥēńţĩćàţĩōń. Kubeconfig + Ķũƀēćōńƒĩĝ Verify Kubernetes API SSL Certificate + Vēŕĩƒŷ Ķũƀēŕńēţēś ÀƤĨ ŚŚĹ Ćēŕţĩƒĩćàţē New outpost integration + Ńēŵ ōũţƥōśţ ĩńţēĝŕàţĩōń Create a new outpost integration. + Ćŕēàţē à ńēŵ ōũţƥōśţ ĩńţēĝŕàţĩōń. State + Śţàţē Unhealthy + Ũńĥēàĺţĥŷ Outpost integration(s) + Ōũţƥōśţ ĩńţēĝŕàţĩōń(ś) Successfully generated certificate-key pair. + Śũććēśśƒũĺĺŷ ĝēńēŕàţēď ćēŕţĩƒĩćàţē-ķēŷ ƥàĩŕ. Common Name + Ćōmmōń Ńàmē Subject-alt name + ŚũƀĴēćţ-àĺţ ńàmē Optional, comma-separated SubjectAlt Names. + Ōƥţĩōńàĺ, ćōmmà-śēƥàŕàţēď ŚũƀĴēćţÀĺţ Ńàmēś. Validity days + Vàĺĩďĩţŷ ďàŷś Successfully updated certificate-key pair. + Śũććēśśƒũĺĺŷ ũƥďàţēď ćēŕţĩƒĩćàţē-ķēŷ ƥàĩŕ. Successfully created certificate-key pair. + Śũććēśśƒũĺĺŷ ćŕēàţēď ćēŕţĩƒĩćàţē-ķēŷ ƥàĩŕ. PEM-encoded Certificate data. + ƤĒM-ēńćōďēď Ćēŕţĩƒĩćàţē ďàţà. Optional Private Key. If this is set, you can use this keypair for encryption. + Ōƥţĩōńàĺ Ƥŕĩvàţē Ķēŷ. Ĩƒ ţĥĩś ĩś śēţ, ŷōũ ćàń ũśē ţĥĩś ķēŷƥàĩŕ ƒōŕ ēńćŕŷƥţĩōń. Certificate-Key Pairs + Ćēŕţĩƒĩćàţē-Ķēŷ Ƥàĩŕś Import certificates of external providers or create certificates to sign requests with. + Ĩmƥōŕţ ćēŕţĩƒĩćàţēś ōƒ ēxţēŕńàĺ ƥŕōvĩďēŕś ōŕ ćŕēàţē ćēŕţĩƒĩćàţēś ţō śĩĝń ŕēǫũēśţś ŵĩţĥ. Private key available? + Ƥŕĩvàţē ķēŷ àvàĩĺàƀĺē? Certificate-Key Pair(s) + Ćēŕţĩƒĩćàţē-Ķēŷ Ƥàĩŕ(ś) Managed by authentik + Màńàĝēď ƀŷ àũţĥēńţĩķ Managed by authentik (Discovered) + Màńàĝēď ƀŷ àũţĥēńţĩķ (Ďĩśćōvēŕēď) Yes () + Ŷēś () No + Ńō Update Certificate-Key Pair + Ũƥďàţē Ćēŕţĩƒĩćàţē-Ķēŷ Ƥàĩŕ Certificate Fingerprint (SHA1) + Ćēŕţĩƒĩćàţē Ƒĩńĝēŕƥŕĩńţ (ŚĤÀ1) Certificate Fingerprint (SHA256) + Ćēŕţĩƒĩćàţē Ƒĩńĝēŕƥŕĩńţ (ŚĤÀ256) Certificate Subject + Ćēŕţĩƒĩćàţē ŚũƀĴēćţ Download Certificate + Ďōŵńĺōàď Ćēŕţĩƒĩćàţē Download Private key + Ďōŵńĺōàď Ƥŕĩvàţē ķēŷ Create Certificate-Key Pair + Ćŕēàţē Ćēŕţĩƒĩćàţē-Ķēŷ Ƥàĩŕ Generate + Ĝēńēŕàţē Generate Certificate-Key Pair + Ĝēńēŕàţē Ćēŕţĩƒĩćàţē-Ķēŷ Ƥàĩŕ Successfully updated instance. + Śũććēśśƒũĺĺŷ ũƥďàţēď ĩńśţàńćē. Successfully created instance. + Śũććēśśƒũĺĺŷ ćŕēàţēď ĩńśţàńćē. Disabled blueprints are never applied. + Ďĩśàƀĺēď ƀĺũēƥŕĩńţś àŕē ńēvēŕ àƥƥĺĩēď. Local path + Ĺōćàĺ ƥàţĥ OCI Registry + ŌĆĨ Ŕēĝĩśţŕŷ Internal + Ĩńţēŕńàĺ OCI URL, in the format of oci://registry.domain.tld/path/to/manifest. + ŌĆĨ ŨŔĹ, ĩń ţĥē ƒōŕmàţ ōƒ ōćĩ://ŕēĝĩśţŕŷ.ďōmàĩń.ţĺď/ƥàţĥ/ţō/màńĩƒēśţ. See more about OCI support here: + Śēē mōŕē àƀōũţ ŌĆĨ śũƥƥōŕţ ĥēŕē: Blueprint + ßĺũēƥŕĩńţ Configure the blueprint context, used for templating. + Ćōńƒĩĝũŕē ţĥē ƀĺũēƥŕĩńţ ćōńţēxţ, ũśēď ƒōŕ ţēmƥĺàţĩńĝ. Orphaned + Ōŕƥĥàńēď Blueprints + ßĺũēƥŕĩńţś Automate and template configuration within authentik. + Àũţōmàţē àńď ţēmƥĺàţē ćōńƒĩĝũŕàţĩōń ŵĩţĥĩń àũţĥēńţĩķ. Last applied + Ĺàśţ àƥƥĺĩēď Blueprint(s) + ßĺũēƥŕĩńţ(ś) Update Blueprint + Ũƥďàţē ßĺũēƥŕĩńţ Create Blueprint Instance + Ćŕēàţē ßĺũēƥŕĩńţ Ĩńśţàńćē API Requests + ÀƤĨ Ŕēǫũēśţś Open API Browser + Ōƥēń ÀƤĨ ßŕōŵśēŕ Notifications + Ńōţĩƒĩćàţĩōńś unread + ũńŕēàď Successfully cleared notifications + Śũććēśśƒũĺĺŷ ćĺēàŕēď ńōţĩƒĩćàţĩōńś Clear all + Ćĺēàŕ àĺĺ A newer version of the frontend is available. + À ńēŵēŕ vēŕśĩōń ōƒ ţĥē ƒŕōńţēńď ĩś àvàĩĺàƀĺē. You're currently impersonating . Click to stop. + Ŷōũ'ŕē ćũŕŕēńţĺŷ ĩmƥēŕśōńàţĩńĝ . Ćĺĩćķ ţō śţōƥ. User interface + Ũśēŕ ĩńţēŕƒàćē Dashboards + Ďàśĥƀōàŕďś Events + Ēvēńţś Logs + Ĺōĝś Customisation + Ćũśţōmĩśàţĩōń Directory + Ďĩŕēćţōŕŷ System + Śŷśţēm Certificates + Ćēŕţĩƒĩćàţēś Outpost Integrations + Ōũţƥōśţ Ĩńţēĝŕàţĩōńś API request failed + ÀƤĨ ŕēǫũēśţ ƒàĩĺēď User's avatar + Ũśēŕ'ś àvàţàŕ Something went wrong! Please try again later. + Śōmēţĥĩńĝ ŵēńţ ŵŕōńĝ! Ƥĺēàśē ţŕŷ àĝàĩń ĺàţēŕ. Request ID + Ŕēǫũēśţ ĨĎ You may close this page now. + Ŷōũ màŷ ćĺōśē ţĥĩś ƥàĝē ńōŵ. You're about to be redirect to the following URL. + Ŷōũ'ŕē àƀōũţ ţō ƀē ŕēďĩŕēćţ ţō ţĥē ƒōĺĺōŵĩńĝ ŨŔĹ. Follow redirect + Ƒōĺĺōŵ ŕēďĩŕēćţ Request has been denied. + Ŕēǫũēśţ ĥàś ƀēēń ďēńĩēď. Not you? + Ńōţ ŷōũ? Need an account? + Ńēēď àń àććōũńţ? Sign up. + Śĩĝń ũƥ. Forgot username or password? + Ƒōŕĝōţ ũśēŕńàmē ōŕ ƥàśśŵōŕď? Select one of the sources below to login. + Śēĺēćţ ōńē ōƒ ţĥē śōũŕćēś ƀēĺōŵ ţō ĺōĝĩń. Or + Ōŕ Use a security key + Ũśē à śēćũŕĩţŷ ķēŷ Login to continue to . + Ĺōĝĩń ţō ćōńţĩńũē ţō . Please enter your password + Ƥĺēàśē ēńţēŕ ŷōũŕ ƥàśśŵōŕď Forgot password? + Ƒōŕĝōţ ƥàśśŵōŕď? Application requires following permissions: + Àƥƥĺĩćàţĩōń ŕēǫũĩŕēś ƒōĺĺōŵĩńĝ ƥēŕmĩśśĩōńś: Application already has access to the following permissions: + Àƥƥĺĩćàţĩōń àĺŕēàďŷ ĥàś àććēśś ţō ţĥē ƒōĺĺōŵĩńĝ ƥēŕmĩśśĩōńś: Application requires following new permissions: + Àƥƥĺĩćàţĩōń ŕēǫũĩŕēś ƒōĺĺōŵĩńĝ ńēŵ ƥēŕmĩśśĩōńś: Check your Inbox for a verification email. + Ćĥēćķ ŷōũŕ Ĩńƀōx ƒōŕ à vēŕĩƒĩćàţĩōń ēmàĩĺ. Send Email again. + Śēńď Ēmàĩĺ àĝàĩń. Successfully copied TOTP Config. + Śũććēśśƒũĺĺŷ ćōƥĩēď ŢŌŢƤ Ćōńƒĩĝ. Copy + Ćōƥŷ Code + Ćōďē Please enter your TOTP Code + Ƥĺēàśē ēńţēŕ ŷōũŕ ŢŌŢƤ Ćōďē Duo activation QR code + Ďũō àćţĩvàţĩōń ǪŔ ćōďē Alternatively, if your current device has Duo installed, click on this link: + Àĺţēŕńàţĩvēĺŷ, ĩƒ ŷōũŕ ćũŕŕēńţ ďēvĩćē ĥàś Ďũō ĩńśţàĺĺēď, ćĺĩćķ ōń ţĥĩś ĺĩńķ: Duo activation + Ďũō àćţĩvàţĩōń Check status + Ćĥēćķ śţàţũś Make sure to keep these tokens in a safe place. + Màķē śũŕē ţō ķēēƥ ţĥēśē ţōķēńś ĩń à śàƒē ƥĺàćē. Phone number + Ƥĥōńē ńũmƀēŕ Please enter your Phone number. + Ƥĺēàśē ēńţēŕ ŷōũŕ Ƥĥōńē ńũmƀēŕ. Please enter the code you received via SMS + Ƥĺēàśē ēńţēŕ ţĥē ćōďē ŷōũ ŕēćēĩvēď vĩà ŚMŚ A code has been sent to you via SMS. + À ćōďē ĥàś ƀēēń śēńţ ţō ŷōũ vĩà ŚMŚ. Open your two-factor authenticator app to view your authentication code. + Ōƥēń ŷōũŕ ţŵō-ƒàćţōŕ àũţĥēńţĩćàţōŕ àƥƥ ţō vĩēŵ ŷōũŕ àũţĥēńţĩćàţĩōń ćōďē. Static token + Śţàţĩć ţōķēń Authentication code + Àũţĥēńţĩćàţĩōń ćōďē Please enter your code + Ƥĺēàśē ēńţēŕ ŷōũŕ ćōďē Return to device picker + Ŕēţũŕń ţō ďēvĩćē ƥĩćķēŕ Sending Duo push notification + Śēńďĩńĝ Ďũō ƥũśĥ ńōţĩƒĩćàţĩōń Assertions is empty + Àśśēŕţĩōńś ĩś ēmƥţŷ Error when creating credential: + Ēŕŕōŕ ŵĥēń ćŕēàţĩńĝ ćŕēďēńţĩàĺ: Error when validating assertion on server: + Ēŕŕōŕ ŵĥēń vàĺĩďàţĩńĝ àśśēŕţĩōń ōń śēŕvēŕ: Retry authentication + Ŕēţŕŷ àũţĥēńţĩćàţĩōń Duo push-notifications + Ďũō ƥũśĥ-ńōţĩƒĩćàţĩōńś Receive a push notification on your device. + Ŕēćēĩvē à ƥũśĥ ńōţĩƒĩćàţĩōń ōń ŷōũŕ ďēvĩćē. Authenticator + Àũţĥēńţĩćàţōŕ Use a security key to prove your identity. + Ũśē à śēćũŕĩţŷ ķēŷ ţō ƥŕōvē ŷōũŕ ĩďēńţĩţŷ. Traditional authenticator + Ţŕàďĩţĩōńàĺ àũţĥēńţĩćàţōŕ Use a code-based authenticator. + Ũśē à ćōďē-ƀàśēď àũţĥēńţĩćàţōŕ. Recovery keys + Ŕēćōvēŕŷ ķēŷś In case you can't access any other method. + Ĩń ćàśē ŷōũ ćàń'ţ àććēśś àńŷ ōţĥēŕ mēţĥōď. SMS + ŚMŚ Tokens sent via SMS. + Ţōķēńś śēńţ vĩà ŚMŚ. Select an authentication method. + Śēĺēćţ àń àũţĥēńţĩćàţĩōń mēţĥōď. Stay signed in? + Śţàŷ śĩĝńēď ĩń? Select Yes to reduce the number of times you're asked to sign in. + Śēĺēćţ Ŷēś ţō ŕēďũćē ţĥē ńũmƀēŕ ōƒ ţĩmēś ŷōũ'ŕē àśķēď ţō śĩĝń ĩń. Authenticating with Plex... + Àũţĥēńţĩćàţĩńĝ ŵĩţĥ Ƥĺēx... Waiting for authentication... + Ŵàĩţĩńĝ ƒōŕ àũţĥēńţĩćàţĩōń... If no Plex popup opens, click the button below. + Ĩƒ ńō Ƥĺēx ƥōƥũƥ ōƥēńś, ćĺĩćķ ţĥē ƀũţţōń ƀēĺōŵ. Open login + Ōƥēń ĺōĝĩń Authenticating with Apple... + Àũţĥēńţĩćàţĩńĝ ŵĩţĥ Àƥƥĺē... Retry + Ŕēţŕŷ Enter the code shown on your device. + Ēńţēŕ ţĥē ćōďē śĥōŵń ōń ŷōũŕ ďēvĩćē. Please enter your Code + Ƥĺēàśē ēńţēŕ ŷōũŕ Ćōďē You've successfully authenticated your device. + Ŷōũ'vē śũććēśśƒũĺĺŷ àũţĥēńţĩćàţēď ŷōũŕ ďēvĩćē. Flow inspector + Ƒĺōŵ ĩńśƥēćţōŕ Next stage + Ńēxţ śţàĝē Stage name + Śţàĝē ńàmē Stage kind + Śţàĝē ķĩńď Stage object + Śţàĝē ōƀĴēćţ This flow is completed. + Ţĥĩś ƒĺōŵ ĩś ćōmƥĺēţēď. Plan history + Ƥĺàń ĥĩśţōŕŷ Current plan context + Ćũŕŕēńţ ƥĺàń ćōńţēxţ Session ID + Śēśśĩōń ĨĎ Powered by authentik + Ƥōŵēŕēď ƀŷ àũţĥēńţĩķ Background image + ßàćķĝŕōũńď ĩmàĝē Error creating credential: + Ēŕŕōŕ ćŕēàţĩńĝ ćŕēďēńţĩàĺ: Server validation of credential failed: + Śēŕvēŕ vàĺĩďàţĩōń ōƒ ćŕēďēńţĩàĺ ƒàĩĺēď: Register device + Ŕēĝĩśţēŕ ďēvĩćē Refer to documentation + Ŕēƒēŕ ţō ďōćũmēńţàţĩōń No Applications available. + Ńō Àƥƥĺĩćàţĩōńś àvàĩĺàƀĺē. Either no applications are defined, or you don’t have access to any. + Ēĩţĥēŕ ńō àƥƥĺĩćàţĩōńś àŕē ďēƒĩńēď, ōŕ ŷōũ ďōń’ţ ĥàvē àććēśś ţō àńŷ. My Applications + Mŷ Àƥƥĺĩćàţĩōńś My applications + Mŷ àƥƥĺĩćàţĩōńś Change your password + Ćĥàńĝē ŷōũŕ ƥàśśŵōŕď Change password + Ćĥàńĝē ƥàśśŵōŕď + Save + Śàvē Delete account + Ďēĺēţē àććōũńţ Successfully updated details + Śũććēśśƒũĺĺŷ ũƥďàţēď ďēţàĩĺś Open settings + Ōƥēń śēţţĩńĝś No settings flow configured. + Ńō śēţţĩńĝś ƒĺōŵ ćōńƒĩĝũŕēď. Update details + Ũƥďàţē ďēţàĩĺś Successfully disconnected source + Śũććēśśƒũĺĺŷ ďĩśćōńńēćţēď śōũŕćē Failed to disconnected source: + Ƒàĩĺēď ţō ďĩśćōńńēćţēď śōũŕćē: Disconnect + Ďĩśćōńńēćţ Connect + Ćōńńēćţ Error: unsupported source settings: + Ēŕŕōŕ: ũńśũƥƥōŕţēď śōũŕćē śēţţĩńĝś: Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials. + Ćōńńēćţ ŷōũŕ ũśēŕ àććōũńţ ţō ţĥē śēŕvĩćēś ĺĩśţēď ƀēĺōŵ, ţō àĺĺōŵ ŷōũ ţō ĺōĝĩń ũśĩńĝ ţĥē śēŕvĩćē ĩńśţēàď ōƒ ţŕàďĩţĩōńàĺ ćŕēďēńţĩàĺś. No services available. + Ńō śēŕvĩćēś àvàĩĺàƀĺē. Create App password + Ćŕēàţē Àƥƥ ƥàśśŵōŕď User details + Ũśēŕ ďēţàĩĺś Consent + Ćōńśēńţ MFA Devices + MƑÀ Ďēvĩćēś Connected services + Ćōńńēćţēď śēŕvĩćēś Tokens and App passwords + Ţōķēńś àńď Àƥƥ ƥàśśŵōŕďś Unread notifications + Ũńŕēàď ńōţĩƒĩćàţĩōńś Admin interface + Àďmĩń ĩńţēŕƒàćē Stop impersonation + Śţōƥ ĩmƥēŕśōńàţĩōń Avatar image + Àvàţàŕ ĩmàĝē Failed + Ƒàĩĺēď Unsynced / N/A + Ũńśŷńćēď / Ń/À Outdated outposts + Ōũţďàţēď ōũţƥōśţś Unhealthy outposts + Ũńĥēàĺţĥŷ ōũţƥōśţś Next + Ńēxţ Inactive + Ĩńàćţĩvē Regular user + Ŕēĝũĺàŕ ũśēŕ Activate + Àćţĩvàţē Use Server URI for SNI verification + Ũśē Śēŕvēŕ ŨŔĨ ƒōŕ ŚŃĨ vēŕĩƒĩćàţĩōń Required for servers using TLS 1.3+ + Ŕēǫũĩŕēď ƒōŕ śēŕvēŕś ũśĩńĝ ŢĹŚ 1.3+ Client certificate keypair to authenticate against the LDAP Server's Certificate. + Ćĺĩēńţ ćēŕţĩƒĩćàţē ķēŷƥàĩŕ ţō àũţĥēńţĩćàţē àĝàĩńśţ ţĥē ĹĎÀƤ Śēŕvēŕ'ś Ćēŕţĩƒĩćàţē. The certificate for the above configured Base DN. As a fallback, the provider uses a self-signed certificate. + Ţĥē ćēŕţĩƒĩćàţē ƒōŕ ţĥē àƀōvē ćōńƒĩĝũŕēď ßàśē ĎŃ. Àś à ƒàĺĺƀàćķ, ţĥē ƥŕōvĩďēŕ ũśēś à śēĺƒ-śĩĝńēď ćēŕţĩƒĩćàţē. TLS Server name + ŢĹŚ Śēŕvēŕ ńàmē DNS name for which the above configured certificate should be used. The certificate cannot be detected based on the base DN, as the SSL/TLS negotiation happens before such data is exchanged. + ĎŃŚ ńàmē ƒōŕ ŵĥĩćĥ ţĥē àƀōvē ćōńƒĩĝũŕēď ćēŕţĩƒĩćàţē śĥōũĺď ƀē ũśēď. Ţĥē ćēŕţĩƒĩćàţē ćàńńōţ ƀē ďēţēćţēď ƀàśēď ōń ţĥē ƀàśē ĎŃ, àś ţĥē ŚŚĹ/ŢĹŚ ńēĝōţĩàţĩōń ĥàƥƥēńś ƀēƒōŕē śũćĥ ďàţà ĩś ēxćĥàńĝēď. TLS Client authentication certificate + ŢĹŚ Ćĺĩēńţ àũţĥēńţĩćàţĩōń ćēŕţĩƒĩćàţē Model + Mōďēĺ Match events created by selected model. When left empty, all models are matched. + Màţćĥ ēvēńţś ćŕēàţēď ƀŷ śēĺēćţēď mōďēĺ. Ŵĥēń ĺēƒţ ēmƥţŷ, àĺĺ mōďēĺś àŕē màţćĥēď. Code-based MFA Support + Ćōďē-ƀàśēď MƑÀ Śũƥƥōŕţ When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon. + Ŵĥēń ēńàƀĺēď, ćōďē-ƀàśēď mũĺţĩ-ƒàćţōŕ àũţĥēńţĩćàţĩōń ćàń ƀē ũśēď ƀŷ àƥƥēńďĩńĝ à śēmĩćōĺōń àńď ţĥē ŢŌŢƤ ćōďē ţō ţĥē ƥàśśŵōŕď. Ţĥĩś śĥōũĺď ōńĺŷ ƀē ēńàƀĺēď ĩƒ àĺĺ ũśēŕś ţĥàţ ŵĩĺĺ ƀĩńď ţō ţĥĩś ƥŕōvĩďēŕ ĥàvē à ŢŌŢƤ ďēvĩćē ćōńƒĩĝũŕēď, àś ōţĥēŕŵĩśē à ƥàśśŵōŕď màŷ ĩńćōŕŕēćţĺŷ ƀē ŕēĴēćţēď ĩƒ ĩţ ćōńţàĩńś à śēmĩćōĺōń. User type + Ũśēŕ ţŷƥē Successfully updated license. + Śũććēśśƒũĺĺŷ ũƥďàţēď ĺĩćēńśē. Successfully created license. + Śũććēśśƒũĺĺŷ ćŕēàţēď ĺĩćēńśē. Install ID + Ĩńśţàĺĺ ĨĎ License key + Ĺĩćēńśē ķēŷ Licenses + Ĺĩćēńśēś License(s) + Ĺĩćēńśē(ś) Enterprise is in preview. + Ēńţēŕƥŕĩśē ĩś ĩń ƥŕēvĩēŵ. Cumulative license expiry + Ćũmũĺàţĩvē ĺĩćēńśē ēxƥĩŕŷ Update License + Ũƥďàţē Ĺĩćēńśē Warning: The current user count has exceeded the configured licenses. + Ŵàŕńĩńĝ: Ţĥē ćũŕŕēńţ ũśēŕ ćōũńţ ĥàś ēxćēēďēď ţĥē ćōńƒĩĝũŕēď ĺĩćēńśēś. Click here for more info. + Ćĺĩćķ ĥēŕē ƒōŕ mōŕē ĩńƒō. Enterprise + Ēńţēŕƥŕĩśē Manage enterprise licenses + Màńàĝē ēńţēŕƥŕĩśē ĺĩćēńśēś No licenses found. + Ńō ĺĩćēńśēś ƒōũńď. Send us feedback! + Śēńď ũś ƒēēďƀàćķ! Get a license + Ĝēţ à ĺĩćēńśē Go to Customer Portal + Ĝō ţō Ćũśţōmēŕ Ƥōŕţàĺ Forecast internal users + Ƒōŕēćàśţ ĩńţēŕńàĺ ũśēŕś Estimated user count one year from now based on current internal users and forecasted internal users. + Ēśţĩmàţēď ũśēŕ ćōũńţ ōńē ŷēàŕ ƒŕōm ńōŵ ƀàśēď ōń ćũŕŕēńţ ĩńţēŕńàĺ ũśēŕś àńď ƒōŕēćàśţēď ĩńţēŕńàĺ ũśēŕś. Forecast external users + Ƒōŕēćàśţ ēxţēŕńàĺ ũśēŕś Estimated user count one year from now based on current external users and forecasted external users. + Ēśţĩmàţēď ũśēŕ ćōũńţ ōńē ŷēàŕ ƒŕōm ńōŵ ƀàśēď ōń ćũŕŕēńţ ēxţēŕńàĺ ũśēŕś àńď ƒōŕēćàśţēď ēxţēŕńàĺ ũśēŕś. Install + Ĩńśţàĺĺ Install License + Ĩńśţàĺĺ Ĺĩćēńśē Internal users might be users such as company employees, which will get access to the full Enterprise feature set. + Ĩńţēŕńàĺ ũśēŕś mĩĝĥţ ƀē ũśēŕś śũćĥ àś ćōmƥàńŷ ēmƥĺōŷēēś, ŵĥĩćĥ ŵĩĺĺ ĝēţ àććēśś ţō ţĥē ƒũĺĺ Ēńţēŕƥŕĩśē ƒēàţũŕē śēţ. External users might be external consultants or B2C customers. These users don't get access to enterprise features. + Ēxţēŕńàĺ ũśēŕś mĩĝĥţ ƀē ēxţēŕńàĺ ćōńśũĺţàńţś ōŕ ß2Ć ćũśţōmēŕś. Ţĥēśē ũśēŕś ďōń'ţ ĝēţ àććēśś ţō ēńţēŕƥŕĩśē ƒēàţũŕēś. Service accounts should be used for machine-to-machine authentication or other automations. + Śēŕvĩćē àććōũńţś śĥōũĺď ƀē ũśēď ƒōŕ màćĥĩńē-ţō-màćĥĩńē àũţĥēńţĩćàţĩōń ōŕ ōţĥēŕ àũţōmàţĩōńś. Less details + Ĺēśś ďēţàĩĺś More details + Mōŕē ďēţàĩĺś Remove item + Ŕēmōvē ĩţēm Open API drawer + Ōƥēń ÀƤĨ ďŕàŵēŕ Open Notification drawer + Ōƥēń Ńōţĩƒĩćàţĩōń ďŕàŵēŕ Restart task + Ŕēśţàŕţ ţàśķ Add provider + Àďď ƥŕōvĩďēŕ Open + Ōƥēń Copy token + Ćōƥŷ ţōķēń Add users + Àďď ũśēŕś Add group + Àďď ĝŕōũƥ Import devices + Ĩmƥōŕţ ďēvĩćēś Execute + Ēxēćũţē Show details + Śĥōŵ ďēţàĩĺś Apply + Àƥƥĺŷ Settings + Śēţţĩńĝś Sign out + Śĩĝń ōũţ The number of tokens generated whenever this stage is used. Every token generated per stage execution will be attached to a single static device. + Ţĥē ńũmƀēŕ ōƒ ţōķēńś ĝēńēŕàţēď ŵĥēńēvēŕ ţĥĩś śţàĝē ĩś ũśēď. Ēvēŕŷ ţōķēń ĝēńēŕàţēď ƥēŕ śţàĝē ēxēćũţĩōń ŵĩĺĺ ƀē àţţàćĥēď ţō à śĩńĝĺē śţàţĩć ďēvĩćē. Token length + Ţōķēń ĺēńĝţĥ The length of the individual generated tokens. Can be increased to improve security. + Ţĥē ĺēńĝţĥ ōƒ ţĥē ĩńďĩvĩďũàĺ ĝēńēŕàţēď ţōķēńś. Ćàń ƀē ĩńćŕēàśēď ţō ĩmƥŕōvē śēćũŕĩţŷ. Internal: + Ĩńţēŕńàĺ: External: + Ēxţēŕńàĺ: Statically deny the flow. To use this stage effectively, disable *Evaluate when flow is planned* on the respective binding. + Śţàţĩćàĺĺŷ ďēńŷ ţĥē ƒĺōŵ. Ţō ũśē ţĥĩś śţàĝē ēƒƒēćţĩvēĺŷ, ďĩśàƀĺē *Ēvàĺũàţē ŵĥēń ƒĺōŵ ĩś ƥĺàńńēď* ōń ţĥē ŕēśƥēćţĩvē ƀĩńďĩńĝ. Create and bind Policy + Ćŕēàţē àńď ƀĩńď Ƥōĺĩćŷ Federation and Social login + Ƒēďēŕàţĩōń àńď Śōćĩàĺ ĺōĝĩń Create and bind Stage + Ćŕēàţē àńď ƀĩńď Śţàĝē Flows and Stages + Ƒĺōŵś àńď Śţàĝēś New version available + Ńēŵ vēŕśĩōń àvàĩĺàƀĺē Failure result + Ƒàĩĺũŕē ŕēśũĺţ Pass + Ƥàśś Don't pass + Ďōń'ţ ƥàśś Result used when policy execution fails. + Ŕēśũĺţ ũśēď ŵĥēń ƥōĺĩćŷ ēxēćũţĩōń ƒàĩĺś. Required: User verification must occur. + Ŕēǫũĩŕēď: Ũśēŕ vēŕĩƒĩćàţĩōń mũśţ ōććũŕ. Preferred: User verification is preferred if available, but not required. + Ƥŕēƒēŕŕēď: Ũśēŕ vēŕĩƒĩćàţĩōń ĩś ƥŕēƒēŕŕēď ĩƒ àvàĩĺàƀĺē, ƀũţ ńōţ ŕēǫũĩŕēď. Discouraged: User verification should not occur. + Ďĩśćōũŕàĝēď: Ũśēŕ vēŕĩƒĩćàţĩōń śĥōũĺď ńōţ ōććũŕ. Required: The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur + Ŕēǫũĩŕēď: Ţĥē àũţĥēńţĩćàţōŕ MŨŚŢ ćŕēàţē à ďēďĩćàţēď ćŕēďēńţĩàĺ. Ĩƒ ĩţ ćàńńōţ, ţĥē ŔƤ ĩś ƥŕēƥàŕēď ƒōŕ àń ēŕŕōŕ ţō ōććũŕ Preferred: The authenticator can create and store a dedicated credential, but if it doesn't that's alright too + Ƥŕēƒēŕŕēď: Ţĥē àũţĥēńţĩćàţōŕ ćàń ćŕēàţē àńď śţōŕē à ďēďĩćàţēď ćŕēďēńţĩàĺ, ƀũţ ĩƒ ĩţ ďōēśń'ţ ţĥàţ'ś àĺŕĩĝĥţ ţōō Discouraged: The authenticator should not create a dedicated credential + Ďĩśćōũŕàĝēď: Ţĥē àũţĥēńţĩćàţōŕ śĥōũĺď ńōţ ćŕēàţē à ďēďĩćàţēď ćŕēďēńţĩàĺ Lock the user out of this system + Ĺōćķ ţĥē ũśēŕ ōũţ ōƒ ţĥĩś śŷśţēm Allow the user to log in and use this system + Àĺĺōŵ ţĥē ũśēŕ ţō ĺōĝ ĩń àńď ũśē ţĥĩś śŷśţēm Temporarily assume the identity of this user + Ţēmƥōŕàŕĩĺŷ àśśũmē ţĥē ĩďēńţĩţŷ ōƒ ţĥĩś ũśēŕ Enter a new password for this user + Ēńţēŕ à ńēŵ ƥàśśŵōŕď ƒōŕ ţĥĩś ũśēŕ Create a link for this user to reset their password + Ćŕēàţē à ĺĩńķ ƒōŕ ţĥĩś ũśēŕ ţō ŕēśēţ ţĥēĩŕ ƥàśśŵōŕď WebAuthn requires this page to be accessed via HTTPS. + ŴēƀÀũţĥń ŕēǫũĩŕēś ţĥĩś ƥàĝē ţō ƀē àććēśśēď vĩà ĤŢŢƤŚ. WebAuthn not supported by browser. + ŴēƀÀũţĥń ńōţ śũƥƥōŕţēď ƀŷ ƀŕōŵśēŕ. Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + Ũśē ţĥĩś ƥŕōvĩďēŕ ŵĩţĥ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩķ'ś ƒōŕŵàŕďÀũţĥ. Ēàćĥ àƥƥĺĩćàţĩōń/ďōmàĩń ńēēďś ĩţś ōŵń ƥŕōvĩďēŕ. Àďďĩţĩōńàĺĺŷ, ōń ēàćĥ ďōmàĩń, /ōũţƥōśţ.ĝōàũţĥēńţĩķ.ĩō mũśţ ƀē ŕōũţēď ţō ţĥē ōũţƥōśţ (ŵĥēń ũśĩńĝ à màńàĝēď ōũţƥōśţ, ţĥĩś ĩś ďōńē ƒōŕ ŷōũ). Default relay state + Ďēƒàũĺţ ŕēĺàŷ śţàţē When using IDP-initiated logins, the relay state will be set to this value. + Ŵĥēń ũśĩńĝ ĨĎƤ-ĩńĩţĩàţēď ĺōĝĩńś, ţĥē ŕēĺàŷ śţàţē ŵĩĺĺ ƀē śēţ ţō ţĥĩś vàĺũē. Flow Info + Ƒĺōŵ Ĩńƒō Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). + Śţàĝē ũśēď ţō ćōńƒĩĝũŕē à ŴēƀÀũţĥń àũţĥēńţĩćàţōŕ (ĩ.ē. Ŷũƀĩķēŷ, ƑàćēĨĎ/Ŵĩńďōŵś Ĥēĺĺō). + +<<<<<<< HEAD + + Internal application name used in URLs. + Ĩńţēŕńàĺ àƥƥĺĩćàţĩōń ńàmē ũśēď ĩń ŨŔĹś. + + + Submit + Śũƀmĩţ + + + UI Settings + ŨĨ Śēţţĩńĝś + + + OAuth2/OpenID + ŌÀũţĥ2/ŌƥēńĨĎ + + + Transparent Reverse Proxy + Ţŕàńśƥàŕēńţ Ŕēvēŕśē Ƥŕōxŷ + + + For transparent reverse proxies with required authentication + Ƒōŕ ţŕàńśƥàŕēńţ ŕēvēŕśē ƥŕōxĩēś ŵĩţĥ ŕēǫũĩŕēď àũţĥēńţĩćàţĩōń + + + Forward Auth Single Application + Ƒōŕŵàŕď Àũţĥ Śĩńĝĺē Àƥƥĺĩćàţĩōń + + + For nginx's auth_request or traefix's forwardAuth + Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ + + + Forward Auth Domain Level + Ƒōŕŵàŕď Àũţĥ Ďōmàĩń Ĺēvēĺ + + + For nginx's auth_request or traefix's forwardAuth per root domain + Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ ƥēŕ ŕōōţ ďōmàĩń + + + Configure SAML provider manually + Ćōńƒĩĝũŕē ŚÀMĹ ƥŕōvĩďēŕ màńũàĺĺŷ + + + RADIUS Configuration + ŔÀĎĨŨŚ Ćōńƒĩĝũŕàţĩōń + + + Configure RADIUS provider manually + Ćōńƒĩĝũŕē ŔÀĎĨŨŚ ƥŕōvĩďēŕ màńũàĺĺŷ + + + SCIM configuration + ŚĆĨM ćōńƒĩĝũŕàţĩōń + + + Configure SCIM provider manually + Ćōńƒĩĝũŕē ŚĆĨM ƥŕōvĩďēŕ màńũàĺĺŷ + + + Saving Application... + Śàvĩńĝ Àƥƥĺĩćàţĩōń... + + + Authentik was unable to save this application: + Àũţĥēńţĩķ ŵàś ũńàƀĺē ţō śàvē ţĥĩś àƥƥĺĩćàţĩōń: + + + Your application has been saved + Ŷōũŕ àƥƥĺĩćàţĩōń ĥàś ƀēēń śàvēď + + + In the Application: + Ĩń ţĥē Àƥƥĺĩćàţĩōń: + + + In the Provider: + Ĩń ţĥē Ƥŕōvĩďēŕ: + + + Method's display Name. + Mēţĥōď'ś ďĩśƥĺàŷ Ńàmē. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Ũśē ţĥĩś ƥŕōvĩďēŕ ŵĩţĥ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩķ'ś + ƒōŕŵàŕďÀũţĥ. Ēàćĥ àƥƥĺĩćàţĩōń/ďōmàĩń ńēēďś ĩţś ōŵń ƥŕōvĩďēŕ. + Àďďĩţĩōńàĺĺŷ, ōń ēàćĥ ďōmàĩń, /ōũţƥōśţ.ĝōàũţĥēńţĩķ.ĩō mũśţ ƀē + ŕōũţēď ţō ţĥē ōũţƥōśţ (ŵĥēń ũśĩńĝ à màńàĝēď ōũţƥōśţ, ţĥĩś ĩś ďōńē ƒōŕ ŷōũ). Custom attributes + Ćũśţōm àţţŕĩƀũţēś Don't show this message again. + Ďōń'ţ śĥōŵ ţĥĩś mēśśàĝē àĝàĩń. - - - + + Pseudolocale (for testing) + Ƥśēũďōĺōćàĺē (ƒōŕ ţēśţĩńĝ) + + + Failed to fetch + Ƒàĩĺēď ţō ƒēţćĥ + + + Failed to fetch data. + Ƒàĩĺēď ţō ƒēţćĥ ďàţà. + + + Successfully assigned permission. + Śũććēśśƒũĺĺŷ àśśĩĝńēď ƥēŕmĩśśĩōń. + + + Role + Ŕōĺē + + + Assign + Àśśĩĝń + + + Assign permission to role + Àśśĩĝń ƥēŕmĩśśĩōń ţō ŕōĺē + + + Assign to new role + Àśśĩĝń ţō ńēŵ ŕōĺē + + + Directly assigned + Ďĩŕēćţĺŷ àśśĩĝńēď + + + Assign permission to user + Àśśĩĝń ƥēŕmĩśśĩōń ţō ũśēŕ + + + Assign to new user + Àśśĩĝń ţō ńēŵ ũśēŕ + + + User Object Permissions + Ũśēŕ ŌƀĴēćţ Ƥēŕmĩśśĩōńś + + + Role Object Permissions + Ŕōĺē ŌƀĴēćţ Ƥēŕmĩśśĩōńś + + + Roles + Ŕōĺēś + + + Select roles to grant this groups' users' permissions from the selected roles. + Śēĺēćţ ŕōĺēś ţō ĝŕàńţ ţĥĩś ĝŕōũƥś' ũśēŕś' ƥēŕmĩśśĩōńś ƒŕōm ţĥē śēĺēćţēď ŕōĺēś. + + + Update Permissions + Ũƥďàţē Ƥēŕmĩśśĩōńś + + + Editing is disabled for managed tokens + Ēďĩţĩńĝ ĩś ďĩśàƀĺēď ƒōŕ màńàĝēď ţōķēńś + + + Select permissions to grant + Śēĺēćţ ƥēŕmĩśśĩōńś ţō ĝŕàńţ + + + Permissions to add + Ƥēŕmĩśśĩōńś ţō àďď + + + Select permissions + Śēĺēćţ ƥēŕmĩśśĩōńś + + + Assign permission + Àśśĩĝń ƥēŕmĩśśĩōń + + + Permission(s) + Ƥēŕmĩśśĩōń(ś) + + + Permission + Ƥēŕmĩśśĩōń + + + User doesn't have view permission so description cannot be retrieved. + Ũśēŕ ďōēśń'ţ ĥàvē vĩēŵ ƥēŕmĩśśĩōń śō ďēśćŕĩƥţĩōń ćàńńōţ ƀē ŕēţŕĩēvēď. + + + Assigned permissions + Àśśĩĝńēď ƥēŕmĩśśĩōńś + + + Assigned global permissions + Àśśĩĝńēď ĝĺōƀàĺ ƥēŕmĩśśĩōńś + + + Assigned object permissions + Àśśĩĝńēď ōƀĴēćţ ƥēŕmĩśśĩōńś + + + Successfully updated role. + Śũććēśśƒũĺĺŷ ũƥďàţēď ŕōĺē. + + + Successfully created role. + Śũććēśśƒũĺĺŷ ćŕēàţēď ŕōĺē. + + + Manage roles which grant permissions to objects within authentik. + Màńàĝē ŕōĺēś ŵĥĩćĥ ĝŕàńţ ƥēŕmĩśśĩōńś ţō ōƀĴēćţś ŵĩţĥĩń àũţĥēńţĩķ. + + + Role(s) + Ŕōĺē(ś) + + + Update Role + Ũƥďàţē Ŕōĺē + + + Create Role + Ćŕēàţē Ŕōĺē + + + Role doesn't have view permission so description cannot be retrieved. + Ŕōĺē ďōēśń'ţ ĥàvē vĩēŵ ƥēŕmĩśśĩōń śō ďēśćŕĩƥţĩōń ćàńńōţ ƀē ŕēţŕĩēvēď. + + + Role + Ŕōĺē + + + Role Info + Ŕōĺē Ĩńƒō + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard + + diff --git a/web/xliff/tr.xlf b/web/xliff/tr.xlf index deaddd663..a92c5e669 100644 --- a/web/xliff/tr.xlf +++ b/web/xliff/tr.xlf @@ -1377,10 +1377,6 @@ Slug Kısa İsim - - Internal application name, used in URLs. - URL'lerde kullanılan dahili uygulama adı. - Optionally enter a group name. Applications with identical groups are shown grouped together. @@ -1391,9 +1387,6 @@ Select a provider that this application should use. - - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. @@ -1667,9 +1660,6 @@ NameID attribute - - SCIM provider is in preview. - Warning: Provider is not assigned to an application as backchannel provider. @@ -1683,30 +1673,9 @@ Run sync again Eşzamanlamayı tekrar çalıştır - - Application details - - - Create application - - - Additional UI settings - - - OAuth2/OIDC - Modern applications, APIs and Single-page applications. - - SAML - - - XML-based SSO standard. Use this if your application only supports SAML. - - - Legacy applications which don't natively support SSO. - LDAP LDAP @@ -1714,111 +1683,13 @@ Provide an LDAP interface for applications and users to authenticate against. - - Link - - - Authentication method - - - LDAP details - - - Create service account - - - Create provider - Sağlayıcı oluştur - - - Application Link - - - URL which will be opened when a user clicks on the application. - - - Method details - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - Web application - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - - - Single-page applications - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - Native application - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - - - API - - - Authentication without user interaction, or machine-to-machine authentication. - - - Application type - - - Flow used when users access this application. - - - Proxy details - - - External domain - - - External domain you will be accessing the domain from. - - - Import SAML Metadata - - - Import the metadata document of the applicaation you want to configure. - - - Manual configuration - - - Manually configure SAML - - - SAML details - - - URL that authentik will redirect back to after successful authentication. - - - Import SAML metadata - New application - - Create a new application. - Applications Uygulamalar - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - OAuth2 ve SAML gibi protokolleri kullanan Kimlik Sağlayıcı olarak authentik'i kullanan Harici Uygulamalar. Tüm uygulamalar burada gösterilir, erişemediğiniz uygulamalar bile. - Provider Type Sağlayıcı Türü @@ -5834,11 +5705,213 @@ Bindings to groups/users are checked against the user of the event. Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Custom attributes Don't show this message again. + + + Failed to fetch + + + Failed to fetch data. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + User Object Permissions + + + Role Object Permissions + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Update Permissions + + + Editing is disabled for managed tokens + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + Permission(s) + + + Permission + + + User doesn't have view permission so description cannot be retrieved. + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/zh-Hans.xlf b/web/xliff/zh-Hans.xlf index f1cc96f30..d6a14c3dd 100644 --- a/web/xliff/zh-Hans.xlf +++ b/web/xliff/zh-Hans.xlf @@ -1837,11 +1837,6 @@ Slug Slug - - - Internal application name, used in URLs. - 应用的内部名称,在 URL 中使用。 - Optionally enter a group name. Applications with identical groups are shown grouped together. @@ -1857,11 +1852,6 @@ Select a provider that this application should use. 选择此应用应该使用的提供程序。 - - - Backchannel providers - 反向通道提供程序 - Select backchannel providers which augment the functionality of the main provider. @@ -2227,11 +2217,6 @@ NameID attribute NameID 属性 - - - SCIM provider is in preview. - SCIM 提供程序处于预览状态。 - Warning: Provider is not assigned to an application as backchannel provider. @@ -2252,46 +2237,11 @@ Run sync again 再次运行同步 - - - Application details - 应用程序详情 - - - - Create application - 创建应用程序 - - - - Additional UI settings - 其他界面设置 - - - - OAuth2/OIDC - OAuth2/OIDC - Modern applications, APIs and Single-page applications. 现代应用程序、API 与单页应用程序。 - - - SAML - SAML - - - - XML-based SSO standard. Use this if your application only supports SAML. - 基于 XML 的 SSO 标准。如果您的应用程序仅支持 SAML 则应使用。 - - - - Legacy applications which don't natively support SSO. - 不原生支持 SSO 的传统应用程序。 - LDAP @@ -2302,176 +2252,16 @@ Provide an LDAP interface for applications and users to authenticate against. 为应用程序和用户提供 LDAP 接口以进行身份​​验证。 - - - Link - 链接 - - - - Authentication method - 身份验证方法 - - - - LDAP details - LDAP 详情 - - - - Create service account - 创建服务账户 - - - - Create provider - 创建提供程序 - - - - Application Link - 应用程序链接 - - - - URL which will be opened when a user clicks on the application. - 用户点击应用程序时将打开的 URL。 - - - - Method details - 方法详情 - - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - 此配置可用于通过其他 API 或以编程方式处理 authentik 身份验证。 - - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - 默认情况下,所有服务账户都可以作为此应用程序进行身份验证,只要它们拥有 app-password 类型的有效令牌。 - - - - Web application - Web 应用程序 - - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - 在服务端处理身份验证的应用程序(例如 Python、Go、Rust、Java、PHP) - - - - Single-page applications - 单页应用程序 - - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - 在浏览器内处理身份验证的单页应用程序(例如 Javascript、Angular、React、Vue) - - - - Native application - 原生应用程序 - - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - 重定向用户到非 Web 回调的应用程序(例如 Android、iOS) - - - - API - API - - - - Authentication without user interaction, or machine-to-machine authentication. - 无需用户操作的身份验证,或 M2M(机器到机器)身份验证。 - - - - Application type - 应用程序类型 - - - - Flow used when users access this application. - 用户访问此应用程序时使用的流程。 - - - - Proxy details - 代理详情 - - - - External domain - 外部域名 - - - - External domain you will be accessing the domain from. - 您将从此外部域名访问域名。 - - - - Import SAML Metadata - 导入 SAML 元数据 - - - - Import the metadata document of the applicaation you want to configure. - 导入您要配置的应用程序的元数据文档。 - - - - Manual configuration - 手动配置 - - - - Manually configure SAML - 手动配置 SAML - - - - SAML details - SAML 详情 - - - - URL that authentik will redirect back to after successful authentication. - 身份验证成功后,authentik 将重定向回的 URL。 - - - - Import SAML metadata - 导入 SAML 元数据 - New application 新应用程序 - - - Create a new application. - 创建一个新应用程序。 - Applications 应用程序 - - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - 利用 OAuth2 和 SAML 等协议,使用 authentik 作为身份提供程序的外部应用程序。此处显示了所有应用程序,即使您无法访问的也包括在内。 - Provider Type @@ -7823,15 +7613,282 @@ Bindings to groups/users are checked against the user of the event. Flow Info + 流程信息 Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). + 用来配置 WebAuthn 身份验证器(即 Yubikey、FaceID/Windows Hello)的阶段。 + +<<<<<<< HEAD + + Internal application name used in URLs. + 在 URL 中使用的应用内部名称。 + + + Submit + 提交 + + + UI Settings + 用户界面设置 + + + OAuth2/OpenID + OAuth2/OpenID + + + Transparent Reverse Proxy + 透明反向代理 + + + For transparent reverse proxies with required authentication + 适用于需要验证身份的透明反向代理 + + + Forward Auth Single Application + Forward Auth 单应用 + + + For nginx's auth_request or traefix's forwardAuth + 适用于 nginx 的 auth_request 或 traefik 的 forwardAuth + + + Forward Auth Domain Level + Forward Auth 域名级 + + + For nginx's auth_request or traefix's forwardAuth per root domain + 适用于按根域名配置的 nginx 的 auth_request 或 traefik 的 forwardAuth + + + Configure SAML provider manually + 手动配置 SAML 提供程序 + + + RADIUS Configuration + RADIUS 配置 + + + Configure RADIUS provider manually + 手动配置 RADIUS 提供程序 + + + SCIM configuration + SCIM 配置 + + + Configure SCIM provider manually + 手动配置 SCIM 提供程序 + + + Saving Application... + 正在保存应用程序… + + + Authentik was unable to save this application: + Authentik 无法保存此应用程序: + + + Your application has been saved + 您的应用程序已保存 + + + In the Application: + 在应用程序中: + + + In the Provider: + 在提供程序中: + + + Method's display Name. + 方法的显示名称。 + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + 此提供程序需要与 nginx 的 auth_request 或 traefik 的 forwardAuth + 一起使用。每个应用/域名需要独立的提供程序。 + 此外,在每个域名上,/outpost.goauthentik.io 必须被路由到 + 前哨(如果使用托管前哨,则已自动帮您完成)。 Custom attributes + 自定义属性 Don't show this message again. + 不要再显示此消息。 + + + Failed to fetch + 拉取失败 + + + Failed to fetch data. + 拉取数据失败。 + + + Successfully assigned permission. + 已成功分配权限。 + + + Role + 角色 + + + Assign + 分配 + + + Assign permission to role + 为角色分配权限 + + + Assign to new role + 分配到新角色 + + + Directly assigned + 直接分配 + + + Assign permission to user + 为用户分配权限 + + + Assign to new user + 分配到新用户 + + + User Object Permissions + 用户对象权限 + + + Role Object Permissions + 角色对象权限 + + + Roles + 角色 + + + Select roles to grant this groups' users' permissions from the selected roles. + 选择角色,为该组内用户授予所选角色的权限。 + + + Update Permissions + 更新权限 + + + Editing is disabled for managed tokens + 托管令牌的编辑已被禁用 + + + Select permissions to grant + 选择权限以授予 + + + Permissions to add + 要添加的权限 + + + Select permissions + 选择权限 + + + Assign permission + 分配权限 + + + Permission(s) + 权限 + + + Permission + 权限 + + + User doesn't have view permission so description cannot be retrieved. + 用户不具有查看权限,所以无法获取描述。 + + + Assigned permissions + 分配的权限 + + + Assigned global permissions + 分配的全局权限 + + + Assigned object permissions + 分配的对象权限 + + + Successfully updated role. + 已成功更新角色。 + + + Successfully created role. + 已成功创建角色。 + + + Manage roles which grant permissions to objects within authentik. + 管理向 authentik 中的对象授予权限的角色。 + + + Role(s) + 角色 + + + Update Role + 更新角色 + + + Create Role + 创建角色 + + + Role doesn't have view permission so description cannot be retrieved. + 角色不具有查看权限,所以无法获取描述。 + + + Role + 角色 + + + Role Info + 角色信息 + + + Pseudolocale (for testing) + 伪区域(测试用) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/zh-Hant.xlf b/web/xliff/zh-Hant.xlf index ddb47517e..a338b32ba 100644 --- a/web/xliff/zh-Hant.xlf +++ b/web/xliff/zh-Hant.xlf @@ -1390,10 +1390,6 @@ Slug Slug - - Internal application name, used in URLs. - 应用的内部名称,在URL中使用。 - Optionally enter a group name. Applications with identical groups are shown grouped together. 输入可选的分组名称。分组相同的应用程序会显示在一起。 @@ -1405,9 +1401,6 @@ Select a provider that this application should use. - - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. @@ -1681,9 +1674,6 @@ NameID attribute - - SCIM provider is in preview. - Warning: Provider is not assigned to an application as backchannel provider. @@ -1697,31 +1687,9 @@ Run sync again 再次运行同步 - - Application details - - - Create application - - - Additional UI settings - - - OAuth2/OIDC - Modern applications, APIs and Single-page applications. - - SAML - SAML - - - XML-based SSO standard. Use this if your application only supports SAML. - - - Legacy applications which don't natively support SSO. - LDAP LDAP @@ -1729,114 +1697,13 @@ Provide an LDAP interface for applications and users to authenticate against. - - Link - - - Authentication method - - - LDAP details - LDAP 详情 - - - Create service account - - - Create provider - 创建提供商 - - - Application Link - - - URL which will be opened when a user clicks on the application. - - - Method details - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - Web application - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - - - Single-page applications - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - Native application - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - - - API - - - Authentication without user interaction, or machine-to-machine authentication. - - - Application type - - - Flow used when users access this application. - - - Proxy details - 代理详情 - - - External domain - - - External domain you will be accessing the domain from. - - - Import SAML Metadata - - - Import the metadata document of the applicaation you want to configure. - - - Manual configuration - - - Manually configure SAML - - - SAML details - SAML 详情 - - - URL that authentik will redirect back to after successful authentication. - - - Import SAML metadata - New application - - Create a new application. - Applications 应用程序 - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - 使用 authentik 作为身份提供程序的外部应用程序,利用 OAuth2 和 SAML 等协议。此处显示了所有应用程序,甚至是您无法访问的应用程序。 - Provider Type 提供商类型 @@ -5886,11 +5753,213 @@ Bindings to groups/users are checked against the user of the event. Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Custom attributes Don't show this message again. + + + Failed to fetch + + + Failed to fetch data. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + User Object Permissions + + + Role Object Permissions + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Update Permissions + + + Editing is disabled for managed tokens + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + Permission(s) + + + Permission + + + User doesn't have view permission so description cannot be retrieved. + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/web/xliff/zh_CN.xlf b/web/xliff/zh_CN.xlf index 411ef8e82..5f253af70 100644 --- a/web/xliff/zh_CN.xlf +++ b/web/xliff/zh_CN.xlf @@ -1837,11 +1837,6 @@ Slug Slug - - - Internal application name, used in URLs. - 应用的内部名称,在 URL 中使用。 - Optionally enter a group name. Applications with identical groups are shown grouped together. @@ -1857,11 +1852,6 @@ Select a provider that this application should use. 选择此应用应该使用的提供程序。 - - - Backchannel providers - 反向通道提供程序 - Select backchannel providers which augment the functionality of the main provider. @@ -2227,11 +2217,6 @@ NameID attribute NameID 属性 - - - SCIM provider is in preview. - SCIM 提供程序处于预览状态。 - Warning: Provider is not assigned to an application as backchannel provider. @@ -2252,46 +2237,11 @@ Run sync again 再次运行同步 - - - Application details - 应用程序详情 - - - - Create application - 创建应用程序 - - - - Additional UI settings - 其他界面设置 - - - - OAuth2/OIDC - OAuth2/OIDC - Modern applications, APIs and Single-page applications. 现代应用程序、API 与单页应用程序。 - - - SAML - SAML - - - - XML-based SSO standard. Use this if your application only supports SAML. - 基于 XML 的 SSO 标准。如果您的应用程序仅支持 SAML 则应使用。 - - - - Legacy applications which don't natively support SSO. - 不原生支持 SSO 的传统应用程序。 - LDAP @@ -2302,166 +2252,11 @@ Provide an LDAP interface for applications and users to authenticate against. 为应用程序和用户提供 LDAP 接口以进行身份​​验证。 - - - Link - 链接 - - - - Authentication method - 身份验证方法 - - - - LDAP details - LDAP 详情 - - - - Create service account - 创建服务账户 - - - - Create provider - 创建提供程序 - - - - Application Link - 应用程序链接 - - - - URL which will be opened when a user clicks on the application. - 用户点击应用程序时将打开的 URL。 - - - - Method details - 方法详情 - - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - 此配置可用于通过其他 API 或以编程方式处理 authentik 身份验证。 - - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - 默认情况下,所有服务账户都可以作为此应用程序进行身份验证,只要它们拥有 app-password 类型的有效令牌。 - - - - Web application - Web 应用程序 - - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - 在服务端处理身份验证的应用程序(例如 Python、Go、Rust、Java、PHP) - - - - Single-page applications - 单页应用程序 - - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - 在浏览器内处理身份验证的单页应用程序(例如 Javascript、Angular、React、Vue) - - - - Native application - 原生应用程序 - - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - 重定向用户到非 Web 回调的应用程序(例如 Android、iOS) - - - - API - API - - - - Authentication without user interaction, or machine-to-machine authentication. - 无需用户操作的身份验证,或 M2M(机器到机器)身份验证。 - - - - Application type - 应用程序类型 - - - - Flow used when users access this application. - 用户访问此应用程序时使用的流程。 - - - - Proxy details - 代理详情 - - - - External domain - 外部域名 - - - - External domain you will be accessing the domain from. - 您将从此外部域名访问域名。 - - - - Import SAML Metadata - 导入 SAML 元数据 - - - - Import the metadata document of the applicaation you want to configure. - 导入您要配置的应用程序的元数据文档。 - - - - Manual configuration - 手动配置 - - - - Manually configure SAML - 手动配置 SAML - - - - SAML details - SAML 详情 - - - - URL that authentik will redirect back to after successful authentication. - 身份验证成功后,authentik 将重定向回的 URL。 - - - - Import SAML metadata - 导入 SAML 元数据 - New application 新应用程序 - - - Create a new application. - 创建一个新应用程序。 - Applications @@ -5305,11 +5100,6 @@ doesn't pass when either or both of the selected options are equal or above the When multiple stages are selected, the user can choose which one they want to enroll. 选中多个阶段时,用户可以选择要注册哪个。 - - - Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello). - 用来配置 WebAuthn 身份验证器(即 Yubikey、FaceID/Windows Hello)的阶段。 - User verification @@ -7825,6 +7615,261 @@ Bindings to groups/users are checked against the user of the event. When using IDP-initiated logins, the relay state will be set to this value. 当使用 IDP 发起的登录时,中继状态会被设置为此值。 + + + Flow Info + 流程信息 + + + Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). + 用来配置 WebAuthn 身份验证器(即 Yubikey、FaceID/Windows Hello)的阶段。 + +<<<<<<< HEAD + + Internal application name used in URLs. + 在 URL 中使用的应用内部名称。 + + + Submit + 提交 + + + UI Settings + 用户界面设置 + + + OAuth2/OpenID + OAuth2/OpenID + + + Transparent Reverse Proxy + 透明反向代理 + + + For transparent reverse proxies with required authentication + 适用于需要验证身份的透明反向代理 + + + Forward Auth Single Application + Forward Auth 单应用 + + + For nginx's auth_request or traefix's forwardAuth + 适用于 nginx 的 auth_request 或 traefik 的 forwardAuth + + + Forward Auth Domain Level + Forward Auth 域名级 + + + For nginx's auth_request or traefix's forwardAuth per root domain + 适用于按根域名配置的 nginx 的 auth_request 或 traefik 的 forwardAuth + + + Configure SAML provider manually + 手动配置 SAML 提供程序 + + + RADIUS Configuration + RADIUS 配置 + + + Configure RADIUS provider manually + 手动配置 RADIUS 提供程序 + + + SCIM configuration + SCIM 配置 + + + Configure SCIM provider manually + 手动配置 SCIM 提供程序 + + + Saving Application... + 正在保存应用程序… + + + Authentik was unable to save this application: + Authentik 无法保存此应用程序: + + + Your application has been saved + 您的应用程序已保存 + + + In the Application: + 在应用程序中: + + + In the Provider: + 在提供程序中: + + + Method's display Name. + 方法的显示名称。 + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + 此提供程序需要与 nginx 的 auth_request 或 traefik 的 forwardAuth + 一起使用。每个应用/域名需要独立的提供程序。 + 此外,在每个域名上,/outpost.goauthentik.io 必须被路由到 + 前哨(如果使用托管前哨,则已自动帮您完成)。 + + + Custom attributes + 自定义属性 + + + Don't show this message again. + 不要再显示此消息。 + + + Failed to fetch + 拉取失败 + + + Failed to fetch data. + 拉取数据失败。 + + + Successfully assigned permission. + 已成功分配权限。 + + + Role + 角色 + + + Assign + 分配 + + + Assign permission to role + 为角色分配权限 + + + Assign to new role + 分配到新角色 + + + Directly assigned + 直接分配 + + + Assign permission to user + 为用户分配权限 + + + Assign to new user + 分配到新用户 + + + User Object Permissions + 用户对象权限 + + + Role Object Permissions + 角色对象权限 + + + Roles + 角色 + + + Select roles to grant this groups' users' permissions from the selected roles. + 选择角色,为该组内用户授予所选角色的权限。 + + + Update Permissions + 更新权限 + + + Editing is disabled for managed tokens + 托管令牌的编辑已被禁用 + + + Select permissions to grant + 选择权限以授予 + + + Permissions to add + 要添加的权限 + + + Select permissions + 选择权限 + + + Assign permission + 分配权限 + + + Permission(s) + 权限 + + + Permission + 权限 + + + User doesn't have view permission so description cannot be retrieved. + 用户不具有查看权限,所以无法获取描述。 + + + Assigned permissions + 分配的权限 + + + Assigned global permissions + 分配的全局权限 + + + Assigned object permissions + 分配的对象权限 + + + Successfully updated role. + 已成功更新角色。 + + + Successfully created role. + 已成功创建角色。 + + + Manage roles which grant permissions to objects within authentik. + 管理向 authentik 中的对象授予权限的角色。 + + + Role(s) + 角色 + + + Update Role + 更新角色 + + + Create Role + 创建角色 + + + Role doesn't have view permission so description cannot be retrieved. + 角色不具有查看权限,所以无法获取描述。 + + + Role + 角色 + + + Role Info + 角色信息 + + + Pseudolocale (for testing) + 伪区域(测试用) diff --git a/web/xliff/zh_TW.xlf b/web/xliff/zh_TW.xlf index 5aa3bc105..56799306e 100644 --- a/web/xliff/zh_TW.xlf +++ b/web/xliff/zh_TW.xlf @@ -1390,10 +1390,6 @@ Slug Slug - - Internal application name, used in URLs. - 应用的内部名称,在URL中使用。 - Optionally enter a group name. Applications with identical groups are shown grouped together. 输入可选的分组名称。分组相同的应用程序会显示在一起。 @@ -1405,9 +1401,6 @@ Select a provider that this application should use. - - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. @@ -1681,9 +1674,6 @@ NameID attribute - - SCIM provider is in preview. - Warning: Provider is not assigned to an application as backchannel provider. @@ -1697,31 +1687,9 @@ Run sync again 再次运行同步 - - Application details - - - Create application - - - Additional UI settings - - - OAuth2/OIDC - Modern applications, APIs and Single-page applications. - - SAML - SAML - - - XML-based SSO standard. Use this if your application only supports SAML. - - - Legacy applications which don't natively support SSO. - LDAP LDAP @@ -1729,114 +1697,13 @@ Provide an LDAP interface for applications and users to authenticate against. - - Link - - - Authentication method - - - LDAP details - LDAP 详情 - - - Create service account - - - Create provider - 创建提供商 - - - Application Link - - - URL which will be opened when a user clicks on the application. - - - Method details - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - - - Web application - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - - - Single-page applications - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - - - Native application - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - - - API - - - Authentication without user interaction, or machine-to-machine authentication. - - - Application type - - - Flow used when users access this application. - - - Proxy details - 代理详情 - - - External domain - - - External domain you will be accessing the domain from. - - - Import SAML Metadata - - - Import the metadata document of the applicaation you want to configure. - - - Manual configuration - - - Manually configure SAML - - - SAML details - SAML 详情 - - - URL that authentik will redirect back to after successful authentication. - - - Import SAML metadata - New application - - Create a new application. - Applications 应用程序 - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - 使用 authentik 作为身份提供程序的外部应用程序,利用 OAuth2 和 SAML 等协议。此处显示了所有应用程序,甚至是您无法访问的应用程序。 - Provider Type 提供商类型 @@ -5885,11 +5752,213 @@ Bindings to groups/users are checked against the user of the event. Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). +<<<<<<< HEAD + + Internal application name used in URLs. + + + Submit + + + UI Settings + + + OAuth2/OpenID + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth Single Application + + + For nginx's auth_request or traefix's forwardAuth + + + Forward Auth Domain Level + + + For nginx's auth_request or traefix's forwardAuth per root domain + + + Configure SAML provider manually + + + RADIUS Configuration + + + Configure RADIUS provider manually + + + SCIM configuration + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + In the Application: + + + In the Provider: + + + Method's display Name. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + Custom attributes Don't show this message again. + + + Failed to fetch + + + Failed to fetch data. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + User Object Permissions + + + Role Object Permissions + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Update Permissions + + + Editing is disabled for managed tokens + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + Permission(s) + + + Permission + + + User doesn't have view permission so description cannot be retrieved. + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Pseudolocale (for testing) + + + Create With Wizard + + + One hint, 'New Application Wizard', is currently hidden + + + 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. + + + Deny message + + + Message shown when this stage is run. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard diff --git a/website/blog/2023-10-18-taxed-out-of-security/image1.png b/website/blog/2023-10-18-taxed-out-of-security/image1.png new file mode 100644 index 000000000..e3a329d2f Binary files /dev/null and b/website/blog/2023-10-18-taxed-out-of-security/image1.png differ diff --git a/website/blog/2023-10-18-taxed-out-of-security/image2.png b/website/blog/2023-10-18-taxed-out-of-security/image2.png new file mode 100644 index 000000000..d45ebb1b7 Binary files /dev/null and b/website/blog/2023-10-18-taxed-out-of-security/image2.png differ diff --git a/website/blog/2023-10-18-taxed-out-of-security/image3.png b/website/blog/2023-10-18-taxed-out-of-security/image3.png new file mode 100644 index 000000000..6af01b469 Binary files /dev/null and b/website/blog/2023-10-18-taxed-out-of-security/image3.png differ diff --git a/website/blog/2023-10-18-taxed-out-of-security/image4.png b/website/blog/2023-10-18-taxed-out-of-security/image4.png new file mode 100644 index 000000000..f7811588e Binary files /dev/null and b/website/blog/2023-10-18-taxed-out-of-security/image4.png differ diff --git a/website/blog/2023-10-18-taxed-out-of-security/item.md b/website/blog/2023-10-18-taxed-out-of-security/item.md new file mode 100644 index 000000000..1e7782f2c --- /dev/null +++ b/website/blog/2023-10-18-taxed-out-of-security/item.md @@ -0,0 +1,172 @@ +--- +title: "How small companies get taxed out of security and why the whole industry suffers" +description: "Software vendors have managed to normalize charging exorbitant prices for basic security features." +slug: 2023-10-18-taxed-out-of-security +authors: + - name: Jens Langhammer + title: CTO at Authentik Security Inc + url: https://github.com/BeryJu + image_url: https://github.com/BeryJu.png +tags: + - security tax + - SSO + - SSO tax + - enterprise + - pricing + - identity provider + - security + - authentication +hide_table_of_contents: false +--- + +> **_authentik is an open source Identity Provider that unifies your identity needs into a single platform, replacing Okta, Active Directory, and auth0. Authentik Security is a [public benefit company](https://github.com/OpenCoreVentures/ocv-public-benefit-company/blob/main/ocv-public-benefit-company-charter.md) building on top of the open source project._** + +--- + +Let’s say you’re working at a small startup: You’re the CTO, your CEO is a good friend, and you have a couple of developers working with you from a previous company. You’re building your initial tech stack, and you start – where else? – with GitHub. + +The [pricing](https://github.com/pricing) is simple enough. There’s a pretty feature-rich free plan, but you’re willing to pay up because the Team plan includes features for restricting access to particular branches and protecting secrets. + +But the enterprise plan, the plan that costs more than four times as much per user per month – the plan that seems targeted at, well, enterprises – promises “Security, compliance, and flexible deployment.” + +> **Is security… not for startups?** + +The feature comparison bears this out: Only the enterprise plan offers single-sign-on (SSO) functionality as part of the package – a feature that security experts have long agreed is essential. But don’t get mad at GitHub. + +Do you want [Box](https://www.box.com/pricing)? You’ll have to pay twice as much for external two-factor authentication. + +Do you want [Mailtrap](https://mailtrap.io/pricing/)? The team, premium, and business plans won’t do. Only the enterprise plan, which costs more than $300 per month more than the team plan, offers SSO. + +Do you want [Hubspot’s marketing product, but with SSO?](https://www.hubspot.com/pricing/marketing/enterprise?products=marketing-hub-professional_1&term=annual) Prepare to pay $2,800 more per month than the next cheapest plan. + +And these are only a few examples. [SSO.tax](https://sso.tax/), a website started by Rob Chahin, gathers many more. If you look through, you’ll see companies like [SurveyMonkey](https://www.surveymonkey.com/pricing/details/) and [Webflow](https://webflow.com/pricing) even restrict SSO to enterprise plans with a _Contact Us_ option instead of a price. + +!["pricing page"](./image1.png) + + + +You’ll also notice that many of the listings are outdated (the Hubspot listing was last updated in 2018, for example, but we quoted the current price). + +Many developers are likely already familiar with the concept of an SSO tax, and some are familiar with the broader idea of a security tax. Fewer know why, despite these concepts entering the lexicon, vendors can still get away with quietly restricting basic security and sign-in features to expensive enterprise plans. + +## Three types of security taxes + +Vendors have managed to normalize charging exorbitant prices for basic security features. Here, we’re not even necessarily talking about often complex logging or monitoring features – the mere ability to sign in to the software itself is an opportunity to upcharge. + +It’s a blurry line, but it’s worth distinguishing between _valuable_ features and _value-added_ features. Unlike other features, which are valuable but part of the base product, value-added features add incremental value on top of the base product. So, we expect GitHub to be basically operational on the free plan but look to other plans to see whether we want to add, say, a wiki feature. + +Security taxes are baseline features pretending to be value-added features, and vendors can charge them by bundling them with those features. These security taxes are often exploitative because companies have to pay for more features than they need just to get a security feature that should have been in the base product. + +A baseline feature is turned into a revenue-generation tool often far out of step with its actual maintenance costs.  “If your SSO support is a 10% price hike,” Chahin writes, “you’re not on this list. But these percentage increases are not maintenance costs, they’re revenue generation because you know your customers have no good options.” + +Research from Grip, an identity risk management company, shows how the [lack of good options](https://www.grip.security/blog/why-sso-doesnt-protect-80-of-your-saas) plays out. Grip’s research shows that 80% of the SaaS applications employees use are not in their companies’ SSO portals. In their interviews with CISOs, SSO licensing costs – i.e., the SSO tax – were the top reason. + +The same logic that applies to the SSO tax also applies to two other security taxes: the MFA tax and the user tax. + +Security experts widely agree that SSO is essential for security, but multi-factor authentication is more basic still, making the act of charging extra for MFA even more egregious. But, as we saw in the intro, companies like Box charge extra for multiple authentication methods. + +The user tax is more subtle. When companies charge excessive amounts per-user to secure those users' accounts, users must either choose to pay the security tax or engage in the highly insecure practice of sharing credentials among several individuals. To be clear, many companies charge on a per-user or per-seat basis (including Authentik), so you can’t call it a tax until the additional costs really become exorbitant. + +## Why the anti-SSO tax movement failed + +The SSO tax has become the most recognized of the three security taxes above. + +By now, there seems to be broad acceptance that the SSO tax is unfair – largely thanks to the SSO Wall of Shame – but there hasn’t been much change from software vendors. + +A grassroots effort like the SSO Wall of Shame would seem effective at public embarrassment. Still, even companies that target users who know better, such as [Docker](https://www.docker.com/pricing/) and developers, or [JFrog](https://jfrog.com/pricing/) and security engineers, charge an SSO tax. + +Future efforts against security taxes will have to keep in mind the three reasons the SSO tax movement failed if change is ever going to happen. + +**1. The SSO tax is too profitable and too easy to charge** + +The most obvious reason is also the strongest. The very thing we’re complaining about – vendors charging too much for a feature that isn’t even that expensive to build or maintain – is exactly why they charge it. + +Ben Orenstein, co-founder and CEO of remote pair-programming app Tuple, writes about [why SSO should be “table stakes”](https://tuple.app/blog/sso-should-be-table-stakes) and why so many other companies (including Tuple, before this post) charged it. + +“If you’re a new SaaS founder and you want to maximize your revenue,” he writes, “I recommend you create an enterprise tier, put SSO in it, and charge 2-5x your normal pricing.” He even explains that because “SSO costs close-to-nothing after a little automation, this price increase is all profit.” + +The math is pretty undeniable, proving Chahin’s basic idea: Vendors can add SSO to an enterprise tier and charge much more than it costs to maintain it. + +Patrick McKenzie, formerly from Stripe, has tweeted about the [same logic](https://twitter.com/patio11/status/1481293496506253321?s=20&t=GSqe0KHLuJaY7TYPS-p4_w). “SSO is a segmentation lever,” he writes, “and a particularly powerful one because everybody in the sophisticated-and-well-monied segment is increasingly forced to purchase it.” + +Both McKenzie and Orenstein emphasize customers being “forced” to adopt an SSO plan. Many companies are selling into regulated industries, so they’ll likely be forced to upgrade all of their software to whichever plan includes SSO. + +**2. The PR risk is too low, and security taxes are too normalized** + +Orenstein writes, “People will get a little mad at you, but not much, because just about everyone does this,” and _just about everyone does this_ links to the SSO.tax site. By now, the SSO Wall of Shame is proof the SSO tax exists, not so much a viable effort at change. + +A big part of the explanation is that the Wall of Shame was primarily one person’s effort, whereas the companies that wanted to keep charging these taxes were larger and much more powerful. The vendors charging the SSO tax had the resources to simply outlast the Wall of Shame. + +Many of these vendors also received some benefit of the doubt. SSO generally takes some effort to build and some resources to maintain – especially custom SAML setups – so vendors have been able to rely on a little plausible deniability. + +A few companies have tried to make some attention by removing the SSO tax – including Tuple and [Scalr](https://www.scalr.com/blog/sso-tax) – but none have really gone viral for the effort. + +**3. The “tragedy of the commons” effect** + +The “tragedy of the commons” is an idea that came out of ecological research in the late 1960s. The “commons” refers to a shared resource, such as a water source, and the “tragedy” is that individuals will each use more of the resource than it can withstand. + +Each individual wants to get as much as possible from the commons, but when everyone takes as much water as they want, in this example, the entire resource dies off, and everyone is worse off. + +![!["Cartoon of the tragedy of the commons by Sketchplanatons"]](./image2.png) + +The idea has since spread to politics, economics, and business. If there’s a situation where individual incentives can defeat collective incentives and shared resources can be destroyed by individuals thinking for themselves, a tragedy of the commons effect is likely. + +As the software supply chain has evolved, open-source adoption has grown, and software companies have become increasingly interconnected, software security has begun to resemble a commons. + +In the past, companies considered security in an organization vs. attacker model, as one entity building a perimeter to defend itself against targeted attackers. But in modern security, organizations are so interconnected that attackers can leap from organization to organization and move laterally from low-value vulnerabilities to high-value exploits. + +When attackers [hacked Target in 2013](https://slate.com/technology/2022/04/breached-excerpt-hartzog-solove-target.html#:~:text=In%20caper%20movies%2C%20the%20criminals,party%20vendor%20hired%20by%20Target.), they didn’t go after Target directly; they entered via a third-party vendor Target had hired. And when Log4j became [headline news in 2022](https://builtin.com/cybersecurity/log4j-vulerability-explained), it wasn’t because one attacker found one exploit; a vast range of companies suddenly realized they were vulnerable because they had all adopted the same open-source component. + +The more interconnected organizations are, the more security becomes a commons, and the more the SSO tax becomes a tragedy. Ed Contreras, Chief Information Security Officer at Frost Bank, said it well in an [interview with CISO Series](https://cisoseries.com/we-shame-others-because-were-so-right-about-everything/): “With single sign-on, we’re protecting both of our companies” and that the SSO tax, as a result, is an “atrocity.” + +## Compromise is the only way out + +For the reasons above, the movement to remove the SSO tax has seemingly ground to a halt. Vendors are still profiting, companies are still paying, and the further outdated the Wall of Shame becomes, the less anyone feels ashamed. + +But that doesn’t mean progress hasn’t been made. Coining the term “SSO tax” named the issue and expanding the idea of security taxes has pushed people toward new ways of thinking about security. If pricing plans are to change, however, we need to acknowledge the strong reasons for charging the SSO tax and offer compromises that satisfy all parties. + +### Offer cheaper ways to authenticate + +Sometimes, heated discussions about the SSO tax on Hacker News miss the fact that SSO technology isn’t always easy to build and maintain. + +For example, Klaas Pieter Annema, engineering manager at Sketch, [writes](https://twitter.com/klaaspieter/status/1562353404143435776), “I was briefly EM for the team maintaining SSO at Sketch. Supporting Google and Microsoft is easy. Supporting whatever wonky homebuilt some large enterprises use is a huge time sync [sic].” + +One compromise is to split these two situations apart. Vendors can offer simple ways to provide SSO for cheap or free but charge for the more complex, customized ways. + +Bitrise, for example, offers [standard SSO](https://bitrise.io/plans-pricing) across its Hobby, Starter, and Teams pricing tiers but only offers custom SAML at its Velocity and Enterprise tiers. + +![!["pricing tiers for Bitrise with included free SSO"]](./image3.png) + +### Charge less + +Even in the original Wall of Shame, Chahin writes, “While I’d like people to really consider it a bare minimum feature for business SaaS, I’m OK with it costing a little extra to cover maintenance costs. If your SSO support is a 10% price hike, you’re not on this list.” + +A compromise is already available: Vendors can charge for the labor to offer SSO but not use SSO as a tool for revenue generation. Vendors can charge less outright or move SSO to cheaper pricing tiers. + +As it turns out, this shift might benefit vendors in the long run. According to research from [Gergely Orosz](https://newsletter.pragmaticengineer.com/p/vendor-spend-cuts), nearly 90% of companies now consider it a goal to reduce vendor spend. + +![!["diagram to illustrate a poll showing that nearly 90% of companies now consider it a goal to reduce vendor spend from pragmaticengineer.com"]](./image4.png) + +The SSO tax has become an obvious target. Any vendor charging an SSO tax is more likely to face spending cuts from customers and less likely to get conversions from newly price-conscious customers. + +Orosz writes, “Consider removing the ‘SSO tax’ to boost conversions for smaller companies. CTOs at early-stage companies have mentioned they are selective when onboarding to SaaSes that charge an SSO tax.” + +Orosz also quotes a few anonymized executives, with one CTO saying, “We're trying to roll out SSO, but many SaaS vendors charge a security tax, so we've had to be selective about which services we upgrade.” + +### Unbundle security, support, and value-added features + +Security and value-added features, as we covered earlier, are very different kinds of features. One way vendors disguise the SSO tax is by charging for these features as a bundle; therefore, one way to compromise is to unbundle these features so that vendors can charge for value-added features but not for baseline security features. + +Once vendors unbundle these features, the previous two compromises make more sense: they can either charge less or introduce separate, cheaper SSO features. Similarly, companies can also distinguish between SSO feature costs and SSO support costs. + +In the previous example, when Klaas Pieter Annema, engineering manager at Sketch, mentioned how SSO frequently became a huge time sink, he also wrote that Sketch “ended up with a rotating support role largely to free up time for these customers.” + +When companies refer to the costs of SSO, this cost is often what they’re referring to – not the sheer cost of building and maintaining the feature but the ongoing support costs. That points to another potential compromise: Vendors could charge for an SSO feature with 24/7 support and charge less for an SSO feature that leaves maintenance up to the customer. + +## Security vendors are caught in the middle, but developers can build a way out + +Throughout this article, we’ve hardly mentioned a central party: SSO vendors. Despite the obvious centrality of SSO vendors and SSO products and tools, security vendors have little leverage when it comes to the SSO tax. + +What we can do, however, is argue for a shift in industry norms: As we’ve written before, the buy vs. build framework is outdated, and it’s no longer obvious that companies should be buying by default. + +The SSO tax persists because it’s easy for vendors to charge, and companies don’t consider other options. As companies consider those options and rediscover why [identity is fun](https://goauthentik.io/blog/2023-08-16-lets-make-identity-fun-again), the SSO tax will become less and less viable. diff --git a/website/developer-docs/api/api.md b/website/developer-docs/api/api.md index e6b7b6dc3..7e3877be7 100644 --- a/website/developer-docs/api/api.md +++ b/website/developer-docs/api/api.md @@ -22,4 +22,4 @@ Users can create tokens to authenticate as any user with a static key, which can ### JWT Token -OAuth2 clients can request the scope `goauthentik.io/api`, which allows their OAuth Refresh token to be used to authenticate to the API. +OAuth2 clients can request the scope `goauthentik.io/api`, which allows their OAuth Access token to be used to authenticate to the API. diff --git a/website/developer-docs/setup/full-dev-environment.md b/website/developer-docs/setup/full-dev-environment.md index baf9eeff1..eb4888e8b 100644 --- a/website/developer-docs/setup/full-dev-environment.md +++ b/website/developer-docs/setup/full-dev-environment.md @@ -30,39 +30,65 @@ Depending on your platform, some native dependencies might be required. On macOS As long as [this issue](https://github.com/xmlsec/python-xmlsec/issues/252) about `libxmlsec-1.3.0` is open, a workaround is required to install a compatible version of `libxmlsec1` with brew, see [this comment](https://github.com/xmlsec/python-xmlsec/issues/254#issuecomment-1612005910). ::: -First, you need to create an isolated Python environment. To create the environment and install dependencies, run the following commands in the same directory as your authentik git repository: +1. Create an isolated Python environment. To create the environment and install dependencies, run the following commands in the same directory as your local authentik git repository: ```shell poetry shell # Creates a python virtualenv, and activates it in a new shell -make install # Install all required dependencies for Python and Javascript, including development dependencies +make install # Installs all required dependencies for Python and Javascript, including development dependencies ``` -To configure authentik to use the local databases, we need a local config file. This file can be generated by running `make gen-dev-config`. +2. Configure authentik to use the local databases using a local config file. To generate this file, run the following command in the same directory as your local authentik git repository: -To apply database migrations, run `make migrate`. This is needed after the initial setup, and whenever you fetch new source from upstream. +```shell +make gen-dev-config # Generates a local config file +``` Generally speaking, authentik is a Django application, ran by gunicorn, proxied by a Go application. The Go application serves static files. Most functions and classes have type-hints and docstrings, so it is recommended to install a Python Type-checking Extension in your IDE to navigate around the code. -Before committing code, run `make lint` to ensure your code is formatted well. This also requires `pyright`, which is installed in the `web/` folder to make dependency management easier. +Before committing code, run the following commands in the same directory as your local authentik git repository: -Run `make gen` to generate an updated OpenAPI document for any changes you made. +```shell +make lint # Ensures your code is well-formatted +make gen # Generates an updated OpenAPI Docs for any changes you make +``` + +:::info +Linting also requires `pyright`, which is installed in the `web/` folder to make dependency management easier. +::: ## Frontend Setup By default, no compiled bundle of the frontend is included so this step is required even if you're not developing for the UI. -To build the UI once, run `make web-build`. +To build the UI once, run the following command in the same directory as your local authentik git repository: -Alternatively, if you want to live-edit the UI, you can run `make web-watch` instead. -This will immediately update the UI with any changes you make so you can see the results in real time without needing to rebuild. +```shell +make web-build # Builds the UI once +``` -To format the frontend code, run `make web`. +If you want to live-edit the UI, you can run the following command in the same directory as your local authentik git repository instead, which will immediately update the UI with any changes you make so you can see the results in real time without needing to rebuild: + +```shell +make web-watch # Updates the UI with any changes you make +``` + +To format the frontend code, run the following command in the same directory as your authentik git repository: + +```shell +make web # Formats the frontend code +``` ## Running authentik -Now that the backend and frontend have been setup and built, you can start authentik by running `ak server`. authentik should now be accessible at `http://localhost:9000`. +Now that the backend and frontend have been setup and built, you can start authentik by running the following command in the same directory as your local authentik git repository: + +```shell +ak server # Starts authentik server +``` + +And now, authentik should now be accessible at `http://localhost:9000`. :::info To define a password for the default admin (called **akadmin**), you can manually enter the `/if/flow/initial-setup/` path in the browser address bar to launch the initial flow. diff --git a/website/docs/expressions/_functions.md b/website/docs/expressions/_functions.md index 57624b119..ceddc916c 100644 --- a/website/docs/expressions/_functions.md +++ b/website/docs/expressions/_functions.md @@ -66,7 +66,7 @@ return ak_is_group_member(request.user, name="test_group") Fetch a user matching `**filters`. -Returns "None" if no user was found, otherwise [User](/docs/user-group/user) +Returns "None" if no user was found, otherwise returns the [User](/docs/user-group/user) object. Example: diff --git a/website/docs/expressions/_user.md b/website/docs/expressions/_user.md index cc747ccad..250e15400 100644 --- a/website/docs/expressions/_user.md +++ b/website/docs/expressions/_user.md @@ -1,4 +1,4 @@ -- `user`: The current user. This may be `None` if there is no contextual user. See ([User](../user-group/user.md#object-attributes)) +- `user`: The current user. This may be `None` if there is no contextual user. See [User](../user-group/user/user_ref.md#object-properties). Example: diff --git a/website/docs/flow/context/index.md b/website/docs/flow/context/index.md index a98512c25..e98e4d30d 100644 --- a/website/docs/flow/context/index.md +++ b/website/docs/flow/context/index.md @@ -22,7 +22,7 @@ Keys prefixed with `goauthentik.io` are used internally by authentik and are sub ### Common keys -#### `pending_user` ([User object](../../user-group/user.md)) +#### `pending_user` ([User object](../../user-group/user/user_ref.md#object-properties)) `pending_user` is used by multiple stages. In the context of most flow executions, it represents the data of the user that is executing the flow. This value is not set automatically, it is set via the [Identification stage](../stages/identification/). @@ -98,6 +98,16 @@ URL that the form will be submitted to. Key-value pairs of the data that is included in the form and will be submitted to `url`. +#### Deny stage + +##### `deny_message` (string) + +:::info +Requires authentik 2023.10 +::: + +Optionally overwrite the deny message shown, has a higher priority than the message configured in the stage. + #### User write stage ##### `groups` (List of [Group objects](../../user-group/group.md)) diff --git a/website/docs/installation/beta.mdx b/website/docs/installation/beta.mdx index 7cd4e4664..6f0252267 100644 --- a/website/docs/installation/beta.mdx +++ b/website/docs/installation/beta.mdx @@ -2,7 +2,7 @@ title: Beta versions --- -You can test upcoming authentik versions by switching to the _next_ images. It is recommended to upgrade to the latest stable release before upgrading to Beta images. It is always possible to upgrade from the Beta to the next stable release. +You can test upcoming authentik versions, including major new features that are in "Beta release", by switching to the _next_ images. It is recommended to upgrade to the latest stable release before upgrading to Beta images. It is always possible to upgrade from the Beta to the next stable release. :::warning Downgrading from the Beta is not supported. It is recommended to take a backup before upgrading, or test Beta versions on a separate install. Upgrading from Beta versions to the next release is usually possible, however also not supported. @@ -81,4 +81,8 @@ helm upgrade authentik authentik/authentik -f values.yaml +:::info +If you are upgrading from an older Beta release to the most recent Beta release, you might need to run `kubectl rollout restart deployment`, because Helm needs to recreate the pods in order to pick up the new image (the tag doesn't change). +::: + To verify whether the upgrade was successful, go to your Admin panel and navigate to the Overview dashboard. There, you can check the version number to ensure that you are using the Beta version you intended. diff --git a/website/docs/policies/expression.mdx b/website/docs/policies/expression.mdx index c98803f65..cf59dccfd 100644 --- a/website/docs/policies/expression.mdx +++ b/website/docs/policies/expression.mdx @@ -41,7 +41,7 @@ import Objects from "../expressions/_objects.md"; - `request`: A PolicyRequest object, which has the following properties: - - `request.user`: The current user, against which the policy is applied. See [User](../user-group/user.md#object-attributes) + - `request.user`: The current user, against which the policy is applied. See [User](../user-group/user/user_ref.md#object-properties) :::caution When a policy is executed in the context of a flow, this will be set to the user initiaing request, and will only be changed by a `user_login` stage. For that reason, using this value in authentication flow policies may not return the expected user. Use `context['pending_user']` instead; User Identification and other stages update this value during flow execution. @@ -77,7 +77,7 @@ This includes the following: - `context['prompt_data']`: Data which has been saved from a prompt stage or an external source. (Optional) - `context['application']`: The application the user is in the process of authorizing. (Optional) - `context['source']`: The source the user is authenticating/enrolling with. (Optional) -- `context['pending_user']`: The currently pending user, see [User](../user-group/user.md#object-attributes) +- `context['pending_user']`: The currently pending user, see [User](../user-group/user/user_ref.md#object-properties) - `context['is_restored']`: Contains the flow token when the flow plan was restored from a link, for example the user clicked a link to a flow which was sent by an email stage. (Optional) - `context['auth_method']`: Authentication method (this value is set by password stages) (Optional) diff --git a/website/docs/providers/scim/index.md b/website/docs/providers/scim/index.md index ee8f4c020..51fb4d868 100644 --- a/website/docs/providers/scim/index.md +++ b/website/docs/providers/scim/index.md @@ -4,10 +4,6 @@ title: SCIM Provider SCIM (System for Cross-domain Identity Management) is a set of APIs to provision users and groups. The SCIM provider in authentik supports SCIM 2.0 and can be used to provision and sync users from authentik into other applications. -:::info -The SCIM provider is currently in Preview. -::: - ### Configuration A SCIM provider requires a base URL and a token. SCIM works via HTTP requests, so authentik must be able to reach the specified endpoint. diff --git a/website/docs/user-group/user/create_invite.png b/website/docs/user-group/user/create_invite.png new file mode 100644 index 000000000..3855bb82a Binary files /dev/null and b/website/docs/user-group/user/create_invite.png differ diff --git a/website/docs/user-group/user/index.mdx b/website/docs/user-group/user/index.mdx new file mode 100644 index 000000000..9f15e85ff --- /dev/null +++ b/website/docs/user-group/user/index.mdx @@ -0,0 +1,12 @@ +--- +title: About users +--- + +import DocCardList from "@theme/DocCardList"; +import { useCurrentSidebarCategory } from "@docusaurus/theme-common"; + +In authentik you can create and manage users with fine-tuned access control, session and event details, group membership, super-user rights, impersonation, and password management and recovery. + +To learn more about working with users in authentik, refer to the following topics: + + diff --git a/website/docs/user-group/user/invitations.md b/website/docs/user-group/user/invitations.md new file mode 100644 index 000000000..7387543f5 --- /dev/null +++ b/website/docs/user-group/user/invitations.md @@ -0,0 +1,49 @@ +--- +title: Invitations +description: "Learn how to create an invitation URL for new users to enroll." +--- + +Invitations are another way to create a user, by inviting someone to join your authentik instance, as a new user. With invitations, you can either email an enrollment invitation URL to one or more specific recipients with pre-defined credentials, or you can email a URL to users, who can then log in and define their own credentials. + +:::info +You can also create a policy to see if the invitation was ever used. +::: + +## Create an invitation + +The fastest way to create an invitation is to use our pre-defined `default-enrollment-flow` that has the necessary stages and prompts already included. + +**Step 1. Download the `default-enrollment-flow` file** + +To download the `default-enrollment-flow` file, run this command: + +``` +wget https://raw.githubusercontent.com/goauthentik/authentik/main/website/developer-docs/blueprints/example/flows-enrollment-2-stage.yaml +``` + +Alternatively, use this [link](/blueprints/example/flows-enrollment-2-stage.yaml) to view and save the file. For more details, refer to the [documentation](https://goauthentik.io/docs/flow/examples/flows#enrollment-2-stage). + +**Step 2. Import the `default-enrollment-flow` file** + +In authentik, navigate to the Admin UI, and then click **Flows** in the left navigation pane. + +At the top of the Flows page, click **Import**, and then select the `flows-enrollment-2-stage.yaml` file that you just downloaded. + +**Step 3. Create the invitation object** + +In the Admin UI, navigate to **Directory --> Invitations**, and then click **Create** to open the **Create Invitation** modal. Define the following fields: + +- **Name**: provide a name for your invitation object. +- **Expires**: select a date for when you want the invitation to expire. +- **Flow**: in the drop-down menu, select the **default-enrollment-flow** Flow. +- **Custom attributes**: (_optional_) enter optional key/value pairs here, to pre-define any information about the user that you will invite to enroll. The data entered here is considered as a variable, specifically the `context['prompt_data']` variable. This data is read by the context flow's [prompt stage](../../flow/stages/prompt/index.md) in an expression policy. + +![Create an invitation modal box](./create_invite.png) + +- **Single use**: specify whether or not you want the invitation to expire after a single use. + +Click **Save** to save the new invitation and close the modal and return to the **Invitations** page. + +**Step 3. Email the invitation** + +On the **Invitations** page, click the chevron beside your new invitation, to expand the details. The **Link to use the invitation** displays with the URL. Copy the URL and send it in an email to the people you want to invite to enroll. diff --git a/website/docs/user-group/user/user_basic_operations.md b/website/docs/user-group/user/user_basic_operations.md new file mode 100644 index 000000000..73a834252 --- /dev/null +++ b/website/docs/user-group/user/user_basic_operations.md @@ -0,0 +1,117 @@ +--- +title: Manage users +--- + +The following topics are for the basic management of users: how to create, modify, delete or deactivate users, and using a recovery email. + +### Create a user + +> If you want to automate user creation, you can do that either by [invitations](./invitations.md), [`user_write` stage](../../flow/stages/user_write), or [using the API](/developer-docs/api/browser). + +1. In the Admin interface of your authentik instance, select **Directory > Users** in the left side menu. + +2. Select the folder where you want to create a user. + +3. Click **Create** (for a default user). + +4. Fill in the required fields: + +- **Username**: This value must be unique across your user folders. +- **Path**: The path where the user will be created. It will be automatically populated with the folder you selected in the previous step. + +5. Fill the **_optional_** fields if needed: + +- **Name**: The display name of the user. +- **Email**: The email address of the user. That will be used if there is a [notification rule](../../events/notifications) triggered or for [email stages](../../flow/stages/email). +- **Is active**: Define is the newly created user account is active. Selected by default. +- **Attributes**: Custom attributes definition for the user, in YAML or JSON format. These attributes can be used to enforce additional prompts on authentication stages or define conditions to enforce specific policies if the current implementation does not fit your use case. The value is an empty dictionary by default. + +6. Click **Create** + +You should see a confirmation pop-up on the top-right of the screen that the user has been created, and see the new user in the user list. You can directly click the username if you want to [modify your user](./user_basic_operations#modify-a-user). + +### View user details + +In the **Directory > Users** menu of the Admin interface, you can browse all the users in your authentik instance. + +To view details about a specific user: + +1. In the list of all users, click on the name of the user you want to check. + + This takes you to the **Overview** tab, with basic information about the user, and also quick access to perform basic actions to the user. + +2. To see further details, click any of the other tabs: + +- **Session** shows the active sessions established by the user. If there is any need, you can clean up the connected devices for a user by selecting the device(s) and then clicking **Delete**. This forces the user to authenticate again on the deleted devices. +- **Groups** allows you to manage the group membership of the user. You can find more details on [groups](../group). +- **User events** displays all the events generated by the user during a session, such as login, logout, application authorisation, password reset, user info update, etc. +- **Explicit consent** lists all the permissions the user has given explicitly to an application. Entries will only appear if the user is validating an [explicit consent flow in an OAuth2 provider](../../providers/oauth2/). If you want to delete the explicit consent (because the application is requiring new permissions, or the user has explicitly asked to reset his consent on third-party apps), select the applications and click **Delete**. The user will be asked to again give explicit consent to share information with the application. +- **OAuth Refresh Tokens** lists all the OAuth tokens currently distributed. You can remove the tokens by selecting the applications and then clicking **Delete**. +- **MFA Authenticators** shows all the authentications that the user has registered to their user profile. You can remove the tokens if the user has lost their authenticator and want to enroll a new one. + +## Modify a user + +After the creation of the user, you can edit any parameter defined during the creation. + +To modify a user object, go to **Directory > Users**, and click the edit icon beside the name. + +You can also go into [user details](#view-user-details), and click **Edit**. + +## User recovery + +If a user has lost their credentials, there are several options. + +### Email them a recovery link + +1. In the Admin interface, navigate to **Directory > Users** to display all users. + +2. Either click the name of the user to display the full User details page, or click the chevron (the › symbol) beside their name to expand the toptions. + +3. To generate a recovery link, which you can then copy and paste into an email, click **View recovery link**. + + A pop-up will appear on your browser with the link for you to copy and to send to the user. + +### Automate email to a user + +You can use our automated email to send a link with the URL for the user to reset their password. This option will only work if you have properly [configured a SMTP server during the installation](../../installation/docker-compose#email-configuration-optional-but-recommended) and set an email address for the user. + +1. In the Admin interface, navigate to **Directory > Users** to display all users. + +2. Either click the name of the user to display the full User details page, or click the chevron beside their name to expand the toptions. + +3. To send the automated email to the user, click **Email recovery link**. + +If the user does not receive the email, check if the mail server parameters [are properly configured](../../troubleshooting/emails). + +### Reset the password for the user + +As an Admin, you can simply reset the password for the user. + +1. In the Admin interface, navigate to **Directory > Users** to display all users. + +2. Either click the name of the user to display the full User details page, or click the chevron beside their name to expand the toptions. + +3. To reset the user's password, click **Reset password**, and then define the new value. + +## Deactivate or Delete user + +#### To deactivate a user: + +1. Go into the user list or detail, and click **Deactivate**. + +2. Review the changes and click **Update**. + +The active sessions are revoked and the authentication of the user blocked. You can reactivate the account by following the same procedure. + +#### To delete a user: + +:::caution +This deletion is not reversible, so be sure you do not need to recover any identity data of the user. +You may instead deactivate the account to preserve identity data. +::: + +1. Go into the user list and select one (or multiple users) to delete and click **Delete** on the top-right of the page. + +2. Review the changes and click **Delete**. + +The user list refreshes and no longer displays the removed users. diff --git a/website/docs/user-group/user.md b/website/docs/user-group/user/user_ref.md similarity index 88% rename from website/docs/user-group/user.md rename to website/docs/user-group/user/user_ref.md index 6651b7cf2..88d8a3af5 100644 --- a/website/docs/user-group/user.md +++ b/website/docs/user-group/user/user_ref.md @@ -1,5 +1,5 @@ --- -title: User +title: User properties and attributes --- ## Object properties @@ -19,15 +19,15 @@ The User object has the following properties: - `group_attributes()` Merged attributes of all groups the user is member of and the user's own attributes. - `ak_groups` This is a queryset of all the user's groups. - You can do additional filtering like + You can do additional filtering like: ```python user.ak_groups.filter(name__startswith='test') ``` - see [here](https://docs.djangoproject.com/en/3.1/ref/models/querysets/#id4) + For Django field lookups, see [here](https://docs.djangoproject.com/en/4.2/ref/models/querysets/#id4). - To get the name of all groups, you can do + To get the name of all groups, you can use this command: ```python [group.name for group in user.ak_groups.all()] @@ -72,7 +72,7 @@ Only applies when the token creation is triggered by the user with this attribut ### `goauthentik.io/user/debug`: -See [Troubleshooting access problems](../troubleshooting/access.md), when set, the user gets a more detailed explanation of access decisions. +See [Troubleshooting access problems](../../troubleshooting/access), when set, the user gets a more detailed explanation of access decisions. ### `additionalHeaders`: diff --git a/website/integrations/services/grafana/index.mdx b/website/integrations/services/grafana/index.mdx index f5274d0f4..e6cc7a5a8 100644 --- a/website/integrations/services/grafana/index.mdx +++ b/website/integrations/services/grafana/index.mdx @@ -26,6 +26,66 @@ Create an application in authentik. Create an OAuth2/OpenID provider with the fo Note the Client ID and Client Secret values. Create an application, using the provider you've created above. Note the slug of the application you've created. +## Terraform provider + +```hcl + +data "authentik_flow" "default-provider-authorization-implicit-consent" { + slug = "default-provider-authorization-implicit-consent" +} + +data "authentik_scope_mapping" "scope-email" { + name = "authentik default OAuth Mapping: OpenID 'email'" +} + +data "authentik_scope_mapping" "scope-profile" { + name = "authentik default OAuth Mapping: OpenID 'profile'" +} + +data "authentik_scope_mapping" "scope-openid" { + name = "authentik default OAuth Mapping: OpenID 'openid'" +} + +resource "authentik_provider_oauth2" "grafana" { + name = "Grafana" + # Required. You can use the output of: + # $ openssl rand -hex 16 + client_id = "my_client_id" + + # Optional: will be generated if not provided + # client_secret = "my_client_secret" + + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + + redirect_uris = ["https://grafana.company/login/generic_oauth"] + + property_mappings = [ + data.authentik_scope_mapping.scope-email.id, + data.authentik_scope_mapping.scope-profile.id, + data.authentik_scope_mapping.scope-openid.id, + ] +} + +resource "authentik_application" "grafana" { + name = "Grafana" + slug = "grafana" + protocol_provider = authentik_provider_oauth2.grafana.id +} + +resource "authentik_group" "grafana_admins" { + name = "Grafana Admins" +} + +resource "authentik_group" "grafana_editors" { + name = "Grafana Editors" +} + +resource "authentik_group" "grafana_viewers" { + name = "Grafana Viewers" +} + +``` + ## Grafana import Tabs from "@theme/Tabs"; @@ -36,9 +96,10 @@ import TabItem from "@theme/TabItem"; values={[ {label: 'Docker', value: 'docker'}, {label: 'Standalone', value: 'standalone'}, + {label: 'Helm', value: 'helm'}, ]}> -If your Grafana is running in docker, set the following environment variables: +If your Grafana instance is running in Docker, set the following environment variables: ```yaml environment: @@ -80,6 +141,32 @@ api_url = https://authentik.company/application/o/userinfo/ role_attribute_path = contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer' ``` + + +If you are using a Helm `values.yaml` file instead, you have to set these options: + +```yaml +grafana.ini: + auth: + signout_redirect_url: "https://authentik.company/application/o//end-session/" + oauth_auto_login: true + auth.generic_oauth: + name: authentik + enabled: true + client_id: "" + client_secret: "" + scopes: "openid profile email" + auth_url: "https://authentik.company/application/o/authorize/" + token_url: "https://authentik.company/application/o/token/" + api_url: "https://authentik.company/application/o/userinfo/" + # Optionally map user groups to Grafana roles + role_attribute_path: contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer' +``` + +:::note +For security reasons you shouldn't inline the client_secret in the values, but use a secret instead. For more information, see https://github.com/grafana/helm-charts/blob/main/charts/grafana/README.md#how-to-securely-reference-secrets-in-grafanaini +::: + diff --git a/website/package-lock.json b/website/package-lock.json index e7ecfe5f4..4ccdec6fd 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -211,16 +211,81 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.21.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", @@ -267,11 +332,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -390,9 +455,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } @@ -409,23 +474,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -546,28 +611,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -608,12 +673,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -685,9 +750,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1935,31 +2000,31 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1968,12 +2033,12 @@ } }, "node_modules/@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -14004,11 +14069,63 @@ } }, "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { @@ -14046,11 +14163,11 @@ } }, "@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "requires": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -14137,9 +14254,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-explode-assignable-expression": { "version": "7.18.6", @@ -14150,20 +14267,20 @@ } }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -14251,22 +14368,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { "version": "7.21.0", @@ -14295,12 +14412,12 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -14356,9 +14473,9 @@ } }, "@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -15166,39 +15283,39 @@ } }, "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "requires": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, diff --git a/website/sidebars.js b/website/sidebars.js index 6b8a56bdf..902194492 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -260,7 +260,22 @@ const docsSidebar = { { type: "category", label: "Users & Groups", - items: ["user-group/user", "user-group/group"], + items: [ + { + type: "category", + label: "Users", + link: { + type: "doc", + id: "user-group/user/index", + }, + items: [ + "user-group/user/user_basic_operations", + "user-group/user/user_ref", + "user-group/user/invitations", + ], + }, + "user-group/group", + ], }, { type: "category",