Merge branch 'main' into celery-2-dramatiq
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
		| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 2025.6.1 | ||||
| current_version = 2025.6.2 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))? | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -202,7 +202,7 @@ jobs: | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: web/dist | ||||
|           key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b | ||||
|           key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b | ||||
|       - name: prepare web ui | ||||
|         if: steps.cache-web.outputs.cache-hit != 'true' | ||||
|         working-directory: web | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| from os import environ | ||||
|  | ||||
| __version__ = "2025.6.1" | ||||
| __version__ = "2025.6.2" | ||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -386,8 +386,23 @@ class UserViewSet(UsedByMixin, ModelViewSet): | ||||
|     queryset = User.objects.none() | ||||
|     ordering = ["username"] | ||||
|     serializer_class = UserSerializer | ||||
|     search_fields = ["username", "name", "is_active", "email", "uuid", "attributes"] | ||||
|     filterset_class = UsersFilter | ||||
|     search_fields = ["username", "name", "is_active", "email", "uuid", "attributes"] | ||||
|  | ||||
|     def get_ql_fields(self): | ||||
|         from djangoql.schema import BoolField, StrField | ||||
|  | ||||
|         from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField | ||||
|  | ||||
|         return [ | ||||
|             StrField(User, "username"), | ||||
|             StrField(User, "name"), | ||||
|             StrField(User, "email"), | ||||
|             StrField(User, "path"), | ||||
|             BoolField(User, "is_active", nullable=True), | ||||
|             ChoiceSearchField(User, "type"), | ||||
|             JSONSearchField(User, "attributes"), | ||||
|         ] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         base_qs = User.objects.all().exclude_anonymous() | ||||
|  | ||||
| @ -114,6 +114,7 @@ class TestApplicationsAPI(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             response.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
| @ -167,6 +168,7 @@ class TestApplicationsAPI(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             response.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
|  | ||||
| @ -119,17 +119,17 @@ class TestTrimPasswordHistory(TestCase): | ||||
|             [ | ||||
|                 UserPasswordHistory( | ||||
|                     user=self.user, | ||||
|                     old_password="hunter1",  # nosec B106 | ||||
|                     old_password="hunter1",  # nosec | ||||
|                     created_at=_now - timedelta(days=3), | ||||
|                 ), | ||||
|                 UserPasswordHistory( | ||||
|                     user=self.user, | ||||
|                     old_password="hunter2",  # nosec B106 | ||||
|                     old_password="hunter2",  # nosec | ||||
|                     created_at=_now - timedelta(days=2), | ||||
|                 ), | ||||
|                 UserPasswordHistory( | ||||
|                     user=self.user, | ||||
|                     old_password="hunter3",  # nosec B106 | ||||
|                     old_password="hunter3",  # nosec | ||||
|                     created_at=_now, | ||||
|                 ), | ||||
|             ] | ||||
|  | ||||
							
								
								
									
										0
									
								
								authentik/enterprise/search/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/enterprise/search/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										12
									
								
								authentik/enterprise/search/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								authentik/enterprise/search/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| """Enterprise app config""" | ||||
|  | ||||
| from authentik.enterprise.apps import EnterpriseConfig | ||||
|  | ||||
|  | ||||
| class AuthentikEnterpriseSearchConfig(EnterpriseConfig): | ||||
|     """Enterprise app config""" | ||||
|  | ||||
|     name = "authentik.enterprise.search" | ||||
|     label = "authentik_search" | ||||
|     verbose_name = "authentik Enterprise.Search" | ||||
|     default = True | ||||
							
								
								
									
										125
									
								
								authentik/enterprise/search/fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								authentik/enterprise/search/fields.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | ||||
| """DjangoQL search""" | ||||
|  | ||||
| from collections import OrderedDict, defaultdict | ||||
| from collections.abc import Generator | ||||
|  | ||||
| from django.db import connection | ||||
| from django.db.models import Model, Q | ||||
| from djangoql.compat import text_type | ||||
| from djangoql.schema import StrField | ||||
|  | ||||
|  | ||||
| class JSONSearchField(StrField): | ||||
|     """JSON field for DjangoQL""" | ||||
|  | ||||
|     model: Model | ||||
|  | ||||
|     def __init__(self, model=None, name=None, nullable=None): | ||||
|         # Set this in the constructor to not clobber the type variable | ||||
|         self.type = "relation" | ||||
|         super().__init__(model, name, nullable) | ||||
|  | ||||
|     def get_lookup(self, path, operator, value): | ||||
|         search = "__".join(path) | ||||
|         op, invert = self.get_operator(operator) | ||||
|         q = Q(**{f"{search}{op}": self.get_lookup_value(value)}) | ||||
|         return ~q if invert else q | ||||
|  | ||||
|     def json_field_keys(self) -> Generator[tuple[str]]: | ||||
|         with connection.cursor() as cursor: | ||||
|             cursor.execute( | ||||
|                 f""" | ||||
|                 WITH RECURSIVE "{self.name}_keys" AS ( | ||||
|                     SELECT | ||||
|                         ARRAY[jsonb_object_keys("{self.name}")] AS key_path_array, | ||||
|                         "{self.name}" -> jsonb_object_keys("{self.name}") AS value | ||||
|                     FROM {self.model._meta.db_table} | ||||
|                     WHERE "{self.name}" IS NOT NULL | ||||
|                         AND jsonb_typeof("{self.name}") = 'object' | ||||
|  | ||||
|                     UNION ALL | ||||
|  | ||||
|                     SELECT | ||||
|                         ck.key_path_array || jsonb_object_keys(ck.value), | ||||
|                         ck.value -> jsonb_object_keys(ck.value) AS value | ||||
|                     FROM "{self.name}_keys" ck | ||||
|                     WHERE jsonb_typeof(ck.value) = 'object' | ||||
|                 ), | ||||
|  | ||||
|                 unique_paths AS ( | ||||
|                     SELECT DISTINCT key_path_array | ||||
|                     FROM "{self.name}_keys" | ||||
|                 ) | ||||
|  | ||||
|                 SELECT key_path_array FROM unique_paths; | ||||
|             """  # nosec | ||||
|             ) | ||||
|             return (x[0] for x in cursor.fetchall()) | ||||
|  | ||||
|     def get_nested_options(self) -> OrderedDict: | ||||
|         """Get keys of all nested objects to show autocomplete""" | ||||
|         base_model_name = f"{self.model._meta.app_label}.{self.model._meta.model_name}_{self.name}" | ||||
|  | ||||
|         def recursive_function(parts: list[str], parent_parts: list[str] | None = None): | ||||
|             if not parent_parts: | ||||
|                 parent_parts = [] | ||||
|             path = parts.pop(0) | ||||
|             parent_parts.append(path) | ||||
|             relation_key = "_".join(parent_parts) | ||||
|             if len(parts) > 1: | ||||
|                 out_dict = { | ||||
|                     relation_key: { | ||||
|                         parts[0]: { | ||||
|                             "type": "relation", | ||||
|                             "relation": f"{relation_key}_{parts[0]}", | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 child_paths = recursive_function(parts.copy(), parent_parts.copy()) | ||||
|                 child_paths.update(out_dict) | ||||
|                 return child_paths | ||||
|             else: | ||||
|                 return {relation_key: {parts[0]: {}}} | ||||
|  | ||||
|         relation_structure = defaultdict(dict) | ||||
|  | ||||
|         for relations in self.json_field_keys(): | ||||
|             result = recursive_function([base_model_name] + relations) | ||||
|             for relation_key, value in result.items(): | ||||
|                 for sub_relation_key, sub_value in value.items(): | ||||
|                     if not relation_structure[relation_key].get(sub_relation_key, None): | ||||
|                         relation_structure[relation_key][sub_relation_key] = sub_value | ||||
|                     else: | ||||
|                         relation_structure[relation_key][sub_relation_key].update(sub_value) | ||||
|  | ||||
|         final_dict = defaultdict(dict) | ||||
|  | ||||
|         for key, value in relation_structure.items(): | ||||
|             for sub_key, sub_value in value.items(): | ||||
|                 if not sub_value: | ||||
|                     final_dict[key][sub_key] = { | ||||
|                         "type": "str", | ||||
|                         "nullable": True, | ||||
|                     } | ||||
|                 else: | ||||
|                     final_dict[key][sub_key] = sub_value | ||||
|         return OrderedDict(final_dict) | ||||
|  | ||||
|     def relation(self) -> str: | ||||
|         return f"{self.model._meta.app_label}.{self.model._meta.model_name}_{self.name}" | ||||
|  | ||||
|  | ||||
| class ChoiceSearchField(StrField): | ||||
|     def __init__(self, model=None, name=None, nullable=None): | ||||
|         super().__init__(model, name, nullable, suggest_options=True) | ||||
|  | ||||
|     def get_options(self, search): | ||||
|         result = [] | ||||
|         choices = self._field_choices() | ||||
|         if choices: | ||||
|             search = search.lower() | ||||
|             for c in choices: | ||||
|                 choice = text_type(c[0]) | ||||
|                 if search in choice.lower(): | ||||
|                     result.append(choice) | ||||
|         return result | ||||
							
								
								
									
										53
									
								
								authentik/enterprise/search/pagination.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								authentik/enterprise/search/pagination.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| from rest_framework.response import Response | ||||
|  | ||||
| from authentik.api.pagination import Pagination | ||||
| from authentik.enterprise.search.ql import AUTOCOMPLETE_COMPONENT_NAME, QLSearch | ||||
|  | ||||
|  | ||||
| class AutocompletePagination(Pagination): | ||||
|  | ||||
|     def paginate_queryset(self, queryset, request, view=None): | ||||
|         self.view = view | ||||
|         return super().paginate_queryset(queryset, request, view) | ||||
|  | ||||
|     def get_autocomplete(self): | ||||
|         schema = QLSearch().get_schema(self.request, self.view) | ||||
|         introspections = {} | ||||
|         if hasattr(self.view, "get_ql_fields"): | ||||
|             from authentik.enterprise.search.schema import AKQLSchemaSerializer | ||||
|  | ||||
|             introspections = AKQLSchemaSerializer().serialize( | ||||
|                 schema(self.page.paginator.object_list.model) | ||||
|             ) | ||||
|         return introspections | ||||
|  | ||||
|     def get_paginated_response(self, data): | ||||
|         previous_page_number = 0 | ||||
|         if self.page.has_previous(): | ||||
|             previous_page_number = self.page.previous_page_number() | ||||
|         next_page_number = 0 | ||||
|         if self.page.has_next(): | ||||
|             next_page_number = self.page.next_page_number() | ||||
|         return Response( | ||||
|             { | ||||
|                 "pagination": { | ||||
|                     "next": next_page_number, | ||||
|                     "previous": previous_page_number, | ||||
|                     "count": self.page.paginator.count, | ||||
|                     "current": self.page.number, | ||||
|                     "total_pages": self.page.paginator.num_pages, | ||||
|                     "start_index": self.page.start_index(), | ||||
|                     "end_index": self.page.end_index(), | ||||
|                 }, | ||||
|                 "results": data, | ||||
|                 "autocomplete": self.get_autocomplete(), | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|     def get_paginated_response_schema(self, schema): | ||||
|         final_schema = super().get_paginated_response_schema(schema) | ||||
|         final_schema["properties"]["autocomplete"] = { | ||||
|             "$ref": f"#/components/schemas/{AUTOCOMPLETE_COMPONENT_NAME}" | ||||
|         } | ||||
|         final_schema["required"].append("autocomplete") | ||||
|         return final_schema | ||||
							
								
								
									
										78
									
								
								authentik/enterprise/search/ql.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								authentik/enterprise/search/ql.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| """DjangoQL search""" | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.db.models import QuerySet | ||||
| from djangoql.ast import Name | ||||
| from djangoql.exceptions import DjangoQLError | ||||
| from djangoql.queryset import apply_search | ||||
| from djangoql.schema import DjangoQLSchema | ||||
| from rest_framework.filters import SearchFilter | ||||
| from rest_framework.request import Request | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.enterprise.search.fields import JSONSearchField | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| AUTOCOMPLETE_COMPONENT_NAME = "Autocomplete" | ||||
| AUTOCOMPLETE_SCHEMA = { | ||||
|     "type": "object", | ||||
|     "additionalProperties": {}, | ||||
| } | ||||
|  | ||||
|  | ||||
| class BaseSchema(DjangoQLSchema): | ||||
|     """Base Schema which deals with JSON Fields""" | ||||
|  | ||||
|     def resolve_name(self, name: Name): | ||||
|         model = self.model_label(self.current_model) | ||||
|         root_field = name.parts[0] | ||||
|         field = self.models[model].get(root_field) | ||||
|         # If the query goes into a JSON field, return the root | ||||
|         # field as the JSON field will do the rest | ||||
|         if isinstance(field, JSONSearchField): | ||||
|             # This is a workaround; build_filter will remove the right-most | ||||
|             # entry in the path as that is intended to be the same as the field | ||||
|             # however for JSON that is not the case | ||||
|             if name.parts[-1] != root_field: | ||||
|                 name.parts.append(root_field) | ||||
|             return field | ||||
|         return super().resolve_name(name) | ||||
|  | ||||
|  | ||||
| class QLSearch(SearchFilter): | ||||
|     """rest_framework search filter which uses DjangoQL""" | ||||
|  | ||||
|     @property | ||||
|     def enabled(self): | ||||
|         return apps.get_app_config("authentik_enterprise").enabled() | ||||
|  | ||||
|     def get_search_terms(self, request) -> str: | ||||
|         """ | ||||
|         Search terms are set by a ?search=... query parameter, | ||||
|         and may be comma and/or whitespace delimited. | ||||
|         """ | ||||
|         params = request.query_params.get(self.search_param, "") | ||||
|         params = params.replace("\x00", "")  # strip null characters | ||||
|         return params | ||||
|  | ||||
|     def get_schema(self, request: Request, view) -> BaseSchema: | ||||
|         ql_fields = [] | ||||
|         if hasattr(view, "get_ql_fields"): | ||||
|             ql_fields = view.get_ql_fields() | ||||
|  | ||||
|         class InlineSchema(BaseSchema): | ||||
|             def get_fields(self, model): | ||||
|                 return ql_fields or [] | ||||
|  | ||||
|         return InlineSchema | ||||
|  | ||||
|     def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet: | ||||
|         search_query = self.get_search_terms(request) | ||||
|         schema = self.get_schema(request, view) | ||||
|         if len(search_query) == 0 or not self.enabled: | ||||
|             return super().filter_queryset(request, queryset, view) | ||||
|         try: | ||||
|             return apply_search(queryset, search_query, schema=schema) | ||||
|         except DjangoQLError as exc: | ||||
|             LOGGER.debug("Failed to parse search expression", exc=exc) | ||||
|             return super().filter_queryset(request, queryset, view) | ||||
							
								
								
									
										29
									
								
								authentik/enterprise/search/schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								authentik/enterprise/search/schema.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| from djangoql.serializers import DjangoQLSchemaSerializer | ||||
| from drf_spectacular.generators import SchemaGenerator | ||||
|  | ||||
| from authentik.api.schema import create_component | ||||
| from authentik.enterprise.search.fields import JSONSearchField | ||||
| from authentik.enterprise.search.ql import AUTOCOMPLETE_COMPONENT_NAME, AUTOCOMPLETE_SCHEMA | ||||
|  | ||||
|  | ||||
| class AKQLSchemaSerializer(DjangoQLSchemaSerializer): | ||||
|     def serialize(self, schema): | ||||
|         serialization = super().serialize(schema) | ||||
|         for _, fields in schema.models.items(): | ||||
|             for _, field in fields.items(): | ||||
|                 if not isinstance(field, JSONSearchField): | ||||
|                     continue | ||||
|                 serialization["models"].update(field.get_nested_options()) | ||||
|         return serialization | ||||
|  | ||||
|     def serialize_field(self, field): | ||||
|         result = super().serialize_field(field) | ||||
|         if isinstance(field, JSONSearchField): | ||||
|             result["relation"] = field.relation() | ||||
|         return result | ||||
|  | ||||
|  | ||||
| def postprocess_schema_search_autocomplete(result, generator: SchemaGenerator, **kwargs): | ||||
|     create_component(generator, AUTOCOMPLETE_COMPONENT_NAME, AUTOCOMPLETE_SCHEMA) | ||||
|  | ||||
|     return result | ||||
							
								
								
									
										17
									
								
								authentik/enterprise/search/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								authentik/enterprise/search/settings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| SPECTACULAR_SETTINGS = { | ||||
|     "POSTPROCESSING_HOOKS": [ | ||||
|         "authentik.api.schema.postprocess_schema_responses", | ||||
|         "authentik.enterprise.search.schema.postprocess_schema_search_autocomplete", | ||||
|         "drf_spectacular.hooks.postprocess_schema_enums", | ||||
|     ], | ||||
| } | ||||
|  | ||||
| REST_FRAMEWORK = { | ||||
|     "DEFAULT_PAGINATION_CLASS": "authentik.enterprise.search.pagination.AutocompletePagination", | ||||
|     "DEFAULT_FILTER_BACKENDS": [ | ||||
|         "authentik.enterprise.search.ql.QLSearch", | ||||
|         "authentik.rbac.filters.ObjectFilter", | ||||
|         "django_filters.rest_framework.DjangoFilterBackend", | ||||
|         "rest_framework.filters.OrderingFilter", | ||||
|     ], | ||||
| } | ||||
							
								
								
									
										78
									
								
								authentik/enterprise/search/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								authentik/enterprise/search/tests.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| from json import loads | ||||
| from unittest.mock import PropertyMock, patch | ||||
| from urllib.parse import urlencode | ||||
|  | ||||
| from django.urls import reverse | ||||
| from rest_framework.test import APITestCase | ||||
|  | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
|  | ||||
|  | ||||
| @patch( | ||||
|     "authentik.enterprise.audit.middleware.EnterpriseAuditMiddleware.enabled", | ||||
|     PropertyMock(return_value=True), | ||||
| ) | ||||
| class QLTest(APITestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.user = create_test_admin_user() | ||||
|         # ensure we have more than 1 user | ||||
|         create_test_admin_user() | ||||
|  | ||||
|     def test_search(self): | ||||
|         """Test simple search query""" | ||||
|         self.client.force_login(self.user) | ||||
|         query = f'username = "{self.user.username}"' | ||||
|         res = self.client.get( | ||||
|             reverse( | ||||
|                 "authentik_api:user-list", | ||||
|             ) | ||||
|             + f"?{urlencode({"search": query})}" | ||||
|         ) | ||||
|         self.assertEqual(res.status_code, 200) | ||||
|         content = loads(res.content) | ||||
|         self.assertEqual(content["pagination"]["count"], 1) | ||||
|         self.assertEqual(content["results"][0]["username"], self.user.username) | ||||
|  | ||||
|     def test_no_search(self): | ||||
|         """Ensure works with no search query""" | ||||
|         self.client.force_login(self.user) | ||||
|         res = self.client.get( | ||||
|             reverse( | ||||
|                 "authentik_api:user-list", | ||||
|             ) | ||||
|         ) | ||||
|         self.assertEqual(res.status_code, 200) | ||||
|         content = loads(res.content) | ||||
|         self.assertNotEqual(content["pagination"]["count"], 1) | ||||
|  | ||||
|     def test_search_no_ql(self): | ||||
|         """Test simple search query (no QL)""" | ||||
|         self.client.force_login(self.user) | ||||
|         res = self.client.get( | ||||
|             reverse( | ||||
|                 "authentik_api:user-list", | ||||
|             ) | ||||
|             + f"?{urlencode({"search": self.user.username})}" | ||||
|         ) | ||||
|         self.assertEqual(res.status_code, 200) | ||||
|         content = loads(res.content) | ||||
|         self.assertEqual(content["pagination"]["count"], 1) | ||||
|         self.assertEqual(content["results"][0]["username"], self.user.username) | ||||
|  | ||||
|     def test_search_json(self): | ||||
|         """Test search query with a JSON attribute""" | ||||
|         self.user.attributes = {"foo": {"bar": "baz"}} | ||||
|         self.user.save() | ||||
|         self.client.force_login(self.user) | ||||
|         query = 'attributes.foo.bar = "baz"' | ||||
|         res = self.client.get( | ||||
|             reverse( | ||||
|                 "authentik_api:user-list", | ||||
|             ) | ||||
|             + f"?{urlencode({"search": query})}" | ||||
|         ) | ||||
|         self.assertEqual(res.status_code, 200) | ||||
|         content = loads(res.content) | ||||
|         self.assertEqual(content["pagination"]["count"], 1) | ||||
|         self.assertEqual(content["results"][0]["username"], self.user.username) | ||||
| @ -6,6 +6,7 @@ TENANT_APPS = [ | ||||
|     "authentik.enterprise.providers.google_workspace", | ||||
|     "authentik.enterprise.providers.microsoft_entra", | ||||
|     "authentik.enterprise.providers.ssf", | ||||
|     "authentik.enterprise.search", | ||||
|     "authentik.enterprise.stages.authenticator_endpoint_gdtc", | ||||
|     "authentik.enterprise.stages.mtls", | ||||
|     "authentik.enterprise.stages.source", | ||||
|  | ||||
| @ -132,6 +132,21 @@ class EventViewSet(ModelViewSet): | ||||
|     ] | ||||
|     filterset_class = EventsFilter | ||||
|  | ||||
|     def get_ql_fields(self): | ||||
|         from djangoql.schema import DateTimeField, StrField | ||||
|  | ||||
|         from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField | ||||
|  | ||||
|         return [ | ||||
|             StrField(Event, "app", suggest_options=True), | ||||
|             StrField(Event, "client_ip"), | ||||
|             JSONSearchField(Event, "user"), | ||||
|             JSONSearchField(Event, "brand"), | ||||
|             ChoiceSearchField(Event, "action"), | ||||
|             JSONSearchField(Event, "context"), | ||||
|             DateTimeField(Event, "created", suggest_options=True), | ||||
|         ] | ||||
|  | ||||
|     @extend_schema( | ||||
|         methods=["GET"], | ||||
|         responses={200: EventTopPerUserSerializer(many=True)}, | ||||
|  | ||||
| @ -49,6 +49,7 @@ class TestEndpointsAPI(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             response.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
| @ -101,6 +102,7 @@ class TestEndpointsAPI(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             response.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
|  | ||||
| @ -44,6 +44,7 @@ class TestRBACRoleAPI(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             res.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
|  | ||||
| @ -46,6 +46,7 @@ class TestRBACUserAPI(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             res.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
|  | ||||
| @ -38,6 +38,7 @@ class TestAPIPerms(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             res.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
| @ -73,6 +74,7 @@ class TestAPIPerms(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             res.content.decode(), | ||||
|             { | ||||
|                 "autocomplete": {}, | ||||
|                 "pagination": { | ||||
|                     "next": 0, | ||||
|                     "previous": 0, | ||||
|  | ||||
| @ -362,8 +362,7 @@ DRAMATIQ = { | ||||
|             "dramatiq.middleware.time_limit.TimeLimit", | ||||
|             { | ||||
|                 # 5 minutes task timeout by default for all tasks | ||||
|                 "time_limit": 600 | ||||
|                 * 1000, | ||||
|                 "time_limit": 600 * 1000, | ||||
|             }, | ||||
|         ), | ||||
|         ("dramatiq.middleware.shutdown.ShutdownNotifications", {}), | ||||
| @ -450,6 +449,8 @@ _DISALLOWED_ITEMS = [ | ||||
|     "INSTALLED_APPS", | ||||
|     "MIDDLEWARE", | ||||
|     "AUTHENTICATION_BACKENDS", | ||||
|     "SPECTACULAR_SETTINGS", | ||||
|     "REST_FRAMEWORK", | ||||
| ] | ||||
|  | ||||
| SILENCED_SYSTEM_CHECKS = [ | ||||
| @ -472,6 +473,8 @@ def _update_settings(app_path: str): | ||||
|         TENANT_APPS.extend(getattr(settings_module, "TENANT_APPS", [])) | ||||
|         MIDDLEWARE.extend(getattr(settings_module, "MIDDLEWARE", [])) | ||||
|         AUTHENTICATION_BACKENDS.extend(getattr(settings_module, "AUTHENTICATION_BACKENDS", [])) | ||||
|         SPECTACULAR_SETTINGS.update(getattr(settings_module, "SPECTACULAR_SETTINGS", {})) | ||||
|         REST_FRAMEWORK.update(getattr(settings_module, "REST_FRAMEWORK", {})) | ||||
|         for _attr in dir(settings_module): | ||||
|             if not _attr.startswith("__") and _attr not in _DISALLOWED_ITEMS: | ||||
|                 globals()[_attr] = getattr(settings_module, _attr) | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|     "$schema": "http://json-schema.org/draft-07/schema", | ||||
|     "$id": "https://goauthentik.io/blueprints/schema.json", | ||||
|     "type": "object", | ||||
|     "title": "authentik 2025.6.1 Blueprint schema", | ||||
|     "title": "authentik 2025.6.2 Blueprint schema", | ||||
|     "required": [ | ||||
|         "version", | ||||
|         "entries" | ||||
| @ -7385,6 +7385,7 @@ | ||||
|                         "authentik.enterprise.providers.google_workspace", | ||||
|                         "authentik.enterprise.providers.microsoft_entra", | ||||
|                         "authentik.enterprise.providers.ssf", | ||||
|                         "authentik.enterprise.search", | ||||
|                         "authentik.enterprise.stages.authenticator_endpoint_gdtc", | ||||
|                         "authentik.enterprise.stages.mtls", | ||||
|                         "authentik.enterprise.stages.source", | ||||
|  | ||||
| @ -31,7 +31,7 @@ services: | ||||
|     volumes: | ||||
|       - redis:/data | ||||
|   server: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2} | ||||
|     restart: unless-stopped | ||||
|     command: server | ||||
|     environment: | ||||
| @ -55,7 +55,7 @@ services: | ||||
|       redis: | ||||
|         condition: service_healthy | ||||
|   worker: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2} | ||||
|     restart: unless-stopped | ||||
|     command: worker | ||||
|     environment: | ||||
|  | ||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @ -18,7 +18,7 @@ require ( | ||||
| 	github.com/gorilla/sessions v1.4.0 | ||||
| 	github.com/gorilla/websocket v1.5.3 | ||||
| 	github.com/grafana/pyroscope-go v1.2.2 | ||||
| 	github.com/jellydator/ttlcache/v3 v3.3.0 | ||||
| 	github.com/jellydator/ttlcache/v3 v3.4.0 | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 | ||||
| 	github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 | ||||
| 	github.com/pires/go-proxyproto v0.8.1 | ||||
| @ -29,7 +29,7 @@ require ( | ||||
| 	github.com/spf13/cobra v1.9.1 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/wwt/guac v1.3.2 | ||||
| 	goauthentik.io/api/v3 v3.2025061.2 | ||||
| 	goauthentik.io/api/v3 v3.2025062.1 | ||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||
| 	golang.org/x/oauth2 v0.30.0 | ||||
| 	golang.org/x/sync v0.15.0 | ||||
|  | ||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							| @ -203,8 +203,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 | ||||
| github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= | ||||
| github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= | ||||
| github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= | ||||
| github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= | ||||
| github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= | ||||
| github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= | ||||
| github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= | ||||
| github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||||
| github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||
| @ -298,8 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y | ||||
| go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | ||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||
| goauthentik.io/api/v3 v3.2025061.2 h1:bKmrl82Gz6J8lz3f+QIH9g+MEkl3MvkMXF34GktesA0= | ||||
| goauthentik.io/api/v3 v3.2025061.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| goauthentik.io/api/v3 v3.2025062.1 h1:spvILDpDDWJNO3pM6QGqmryx6NvSchr1E8H60J/XUCA= | ||||
| goauthentik.io/api/v3 v3.2025062.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
|  | ||||
| @ -33,4 +33,4 @@ func UserAgent() string { | ||||
| 	return fmt.Sprintf("authentik@%s", FullVersion()) | ||||
| } | ||||
|  | ||||
| const VERSION = "2025.6.1" | ||||
| const VERSION = "2025.6.2" | ||||
|  | ||||
| @ -26,7 +26,7 @@ Parameters: | ||||
|     Description: authentik Docker image | ||||
|   AuthentikVersion: | ||||
|     Type: String | ||||
|     Default: 2025.6.1 | ||||
|     Default: 2025.6.2 | ||||
|     Description: authentik Docker image tag | ||||
|   AuthentikServerCPU: | ||||
|     Type: Number | ||||
|  | ||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| { | ||||
|     "name": "@goauthentik/authentik", | ||||
|     "version": "2025.6.1", | ||||
|     "version": "2025.6.2", | ||||
|     "lockfileVersion": 3, | ||||
|     "requires": true, | ||||
|     "packages": { | ||||
|         "": { | ||||
|             "name": "@goauthentik/authentik", | ||||
|             "version": "2025.6.1", | ||||
|             "version": "2025.6.2", | ||||
|             "devDependencies": { | ||||
|                 "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|                 "prettier": "^3.3.3", | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@goauthentik/authentik", | ||||
|     "version": "2025.6.1", | ||||
|     "version": "2025.6.2", | ||||
|     "private": true, | ||||
|     "type": "module", | ||||
|     "devDependencies": { | ||||
|  | ||||
							
								
								
									
										126
									
								
								packages/eslint-config/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										126
									
								
								packages/eslint-config/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -576,17 +576,17 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", | ||||
|             "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", | ||||
|             "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/regexpp": "^4.10.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/type-utils": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/type-utils": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "graphemer": "^1.4.0", | ||||
|                 "ignore": "^7.0.0", | ||||
|                 "natural-compare": "^1.4.0", | ||||
| @ -600,7 +600,7 @@ | ||||
|                 "url": "https://opencollective.com/typescript-eslint" | ||||
|             }, | ||||
|             "peerDependencies": { | ||||
|                 "@typescript-eslint/parser": "^8.34.0", | ||||
|                 "@typescript-eslint/parser": "^8.34.1", | ||||
|                 "eslint": "^8.57.0 || ^9.0.0", | ||||
|                 "typescript": ">=4.8.4 <5.9.0" | ||||
|             } | ||||
| @ -616,16 +616,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/parser": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", | ||||
|             "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", | ||||
|             "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -641,14 +641,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/project-service": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", | ||||
|             "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", | ||||
|             "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.0", | ||||
|                 "@typescript-eslint/types": "^8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.1", | ||||
|                 "@typescript-eslint/types": "^8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -663,14 +663,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/scope-manager": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", | ||||
|             "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", | ||||
|             "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -681,9 +681,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/tsconfig-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -698,14 +698,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/type-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "ts-api-utils": "^2.1.0" | ||||
|             }, | ||||
| @ -722,9 +722,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/types": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", | ||||
|             "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -736,16 +736,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", | ||||
|             "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/project-service": "8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/project-service": "8.34.1", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "fast-glob": "^3.3.2", | ||||
|                 "is-glob": "^4.0.3", | ||||
| @ -765,9 +765,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { | ||||
|             "version": "2.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", | ||||
|             "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", | ||||
|             "version": "2.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | ||||
|             "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
| @ -804,16 +804,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.7.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0" | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -828,14 +828,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/visitor-keys": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", | ||||
|             "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", | ||||
|             "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "eslint-visitor-keys": "^4.2.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "eslint-visitor-keys": "^4.2.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -4035,15 +4035,15 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/typescript-eslint": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", | ||||
|             "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", | ||||
|             "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.0", | ||||
|                 "@typescript-eslint/parser": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0" | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.1", | ||||
|                 "@typescript-eslint/parser": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| [project] | ||||
| name = "authentik" | ||||
| version = "2025.6.1" | ||||
| version = "2025.6.2" | ||||
| description = "" | ||||
| authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }] | ||||
| requires-python = "==3.13.*" | ||||
| @ -23,11 +23,12 @@ dependencies = [ | ||||
|     "django-pglock==1.7.2", | ||||
|     "django-pgtrigger==4.15.2", | ||||
|     "django-prometheus==2.3.1", | ||||
|     "django-redis==5.4.0", | ||||
|     "django-redis==6.0.0", | ||||
|     "django-storages[s3]==1.14.6", | ||||
|     "django-tenants==3.8.0", | ||||
|     "djangorestframework==3.16.0", | ||||
|     "djangoql==0.18.1", | ||||
|     "djangorestframework-guardian==0.3.0", | ||||
|     "djangorestframework==3.16.0", | ||||
|     "docker==7.1.0", | ||||
|     "dramatiq[watch]==1.17.1", | ||||
|     "drf-orjson-renderer==1.7.3", | ||||
| @ -70,7 +71,7 @@ dependencies = [ | ||||
|     "urllib3<3", | ||||
|     "uvicorn[standard]==0.34.3", | ||||
|     "watchdog==6.0.0", | ||||
|     "webauthn==2.5.2", | ||||
|     "webauthn==2.6.0", | ||||
|     "wsproto==1.2.0", | ||||
|     "xmlsec==1.3.15", | ||||
|     "zxcvbn==4.5.0", | ||||
|  | ||||
							
								
								
									
										417
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										417
									
								
								schema.yml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										59
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										59
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -159,7 +159,7 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "authentik" | ||||
| version = "2025.6.1" | ||||
| version = "2025.6.2" | ||||
| source = { editable = "." } | ||||
| dependencies = [ | ||||
|     { name = "argon2-cffi" }, | ||||
| @ -183,6 +183,7 @@ dependencies = [ | ||||
|     { name = "django-redis" }, | ||||
|     { name = "django-storages", extra = ["s3"] }, | ||||
|     { name = "django-tenants" }, | ||||
|     { name = "djangoql" }, | ||||
|     { name = "djangorestframework" }, | ||||
|     { name = "djangorestframework-guardian" }, | ||||
|     { name = "docker" }, | ||||
| @ -280,9 +281,10 @@ requires-dist = [ | ||||
|     { name = "django-pglock", specifier = "==1.7.2" }, | ||||
|     { name = "django-pgtrigger", specifier = "==4.15.2" }, | ||||
|     { name = "django-prometheus", specifier = "==2.3.1" }, | ||||
|     { name = "django-redis", specifier = "==5.4.0" }, | ||||
|     { name = "django-redis", specifier = "==6.0.0" }, | ||||
|     { name = "django-storages", extras = ["s3"], specifier = "==1.14.6" }, | ||||
|     { name = "django-tenants", specifier = "==3.8.0" }, | ||||
|     { name = "djangoql", specifier = "==0.18.1" }, | ||||
|     { name = "djangorestframework", git = "https://github.com/goauthentik/django-rest-framework?rev=896722bab969fabc74a08b827da59409cf9f1a4e" }, | ||||
|     { name = "djangorestframework-guardian", specifier = "==0.3.0" }, | ||||
|     { name = "docker", specifier = "==7.1.0" }, | ||||
| @ -327,7 +329,7 @@ requires-dist = [ | ||||
|     { name = "urllib3", specifier = "<3" }, | ||||
|     { name = "uvicorn", extras = ["standard"], specifier = "==0.34.3" }, | ||||
|     { name = "watchdog", specifier = "==6.0.0" }, | ||||
|     { name = "webauthn", specifier = "==2.5.2" }, | ||||
|     { name = "webauthn", specifier = "==2.6.0" }, | ||||
|     { name = "wsproto", specifier = "==1.2.0" }, | ||||
|     { name = "xmlsec", specifier = "==1.3.15" }, | ||||
|     { name = "zxcvbn", specifier = "==4.5.0" }, | ||||
| @ -1055,15 +1057,15 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "django-redis" | ||||
| version = "5.4.0" | ||||
| version = "6.0.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "django" }, | ||||
|     { name = "redis" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/83/9d/2272742fdd9d0a9f0b28cd995b0539430c9467a2192e4de2cea9ea6ad38c/django-redis-5.4.0.tar.gz", hash = "sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42", size = 52567, upload-time = "2023-10-01T20:22:01.221Z" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/08/53/dbcfa1e528e0d6c39947092625b2c89274b5d88f14d357cee53c4d6dbbd4/django_redis-6.0.0.tar.gz", hash = "sha256:2d9cb12a20424a4c4dde082c6122f486628bae2d9c2bee4c0126a4de7fda00dd", size = 56904, upload-time = "2025-06-17T18:15:46.376Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/b7/f1/63caad7c9222c26a62082f4f777de26389233b7574629996098bf6d25a4d/django_redis-5.4.0-py3-none-any.whl", hash = "sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b", size = 31119, upload-time = "2023-10-01T20:21:33.009Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7e/79/055dfcc508cfe9f439d9f453741188d633efa9eab90fc78a67b0ab50b137/django_redis-6.0.0-py3-none-any.whl", hash = "sha256:20bf0063a8abee567eb5f77f375143c32810c8700c0674ced34737f8de4e36c0", size = 33687, upload-time = "2025-06-17T18:15:34.165Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1092,6 +1094,17 @@ dependencies = [ | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/a8/7b/22e3bb79d48e5a4fdcacdcdc27bbc5c2523a2b7892b440bfe229f313d823/django_tenants-3.8.0.tar.gz", hash = "sha256:07d009d5d01be2d65c3f5ddbf323d58d1228838fc1a64fded15c8e5c6f41cf8f", size = 154307, upload-time = "2025-05-23T16:07:24.307Z" } | ||||
|  | ||||
| [[package]] | ||||
| name = "djangoql" | ||||
| version = "0.18.1" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "ply" }, | ||||
| ] | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/9e/0a/83cdb7b9d3b854b98941363153945f6c051b3bc50cd61108a85677c98c3a/djangoql-0.18.1-py2.py3-none-any.whl", hash = "sha256:51b3085a805627ebb43cfd0aa861137cdf8f69cc3c9244699718fe04a6c8e26d", size = 218209, upload-time = "2024-01-08T14:10:47.915Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "djangorestframework" | ||||
| version = "3.16.0" | ||||
| @ -2299,6 +2312,15 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "ply" | ||||
| version = "3.11" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "prometheus-client" | ||||
| version = "0.22.1" | ||||
| @ -3079,6 +3101,25 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tornado" | ||||
| version = "6.5.1" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934, upload-time = "2025-05-22T18:15:38.788Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948, upload-time = "2025-05-22T18:15:20.862Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112, upload-time = "2025-05-22T18:15:22.591Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672, upload-time = "2025-05-22T18:15:24.027Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019, upload-time = "2025-05-22T18:15:25.735Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252, upload-time = "2025-05-22T18:15:27.499Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930, upload-time = "2025-05-22T18:15:29.299Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351, upload-time = "2025-05-22T18:15:31.038Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328, upload-time = "2025-05-22T18:15:32.426Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396, upload-time = "2025-05-22T18:15:34.205Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840, upload-time = "2025-05-22T18:15:36.1Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596, upload-time = "2025-05-22T18:15:37.433Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "trio" | ||||
| version = "0.30.0" | ||||
| @ -3347,7 +3388,7 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "webauthn" | ||||
| version = "2.5.2" | ||||
| version = "2.6.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "asn1crypto" }, | ||||
| @ -3355,9 +3396,9 @@ dependencies = [ | ||||
|     { name = "cryptography" }, | ||||
|     { name = "pyopenssl" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/8d/92/8d2a4eec83d8e7feacdaad37c6eb6eb922100cecce5c14a41d8069a59a03/webauthn-2.5.2.tar.gz", hash = "sha256:09c13dfc1c68c810f32fa4d89b1d37acb9f9ae9091c9d7019e313be4525a95ef", size = 124114, upload-time = "2025-03-07T19:44:05.243Z" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/63/38/5792cb2034673c162a721df0ad65825699516ee0c938a65670ad3cdabf6c/webauthn-2.6.0.tar.gz", hash = "sha256:13cf5b009a64cef569599ffecf24550df1d7c0cd4fbaea870f937148484a80b4", size = 123608, upload-time = "2025-06-16T22:25:26.76Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/7f/fe/f6ae41de9f383439e30b303a67f6f45d2fceabedaedc34c62f74d58c5c73/webauthn-2.5.2-py3-none-any.whl", hash = "sha256:44246e496e617eb5e2f51165046b9f0197fcdf470f69cd6734061a27ba365f8e", size = 71624, upload-time = "2025-03-07T19:44:03.728Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/56/c5/b1bba7f6a50caca77f37003e098f48f8dc68d990aba8a03ac8376016430b/webauthn-2.6.0-py3-none-any.whl", hash = "sha256:459973eb5780c1f41bec42b682acf587456b185733398a0b99a0714705b79447", size = 71189, upload-time = "2025-06-16T22:25:25.535Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
|  | ||||
							
								
								
									
										207
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										207
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -22,16 +22,17 @@ | ||||
|                 "@floating-ui/dom": "^1.6.11", | ||||
|                 "@formatjs/intl-listformat": "^7.7.11", | ||||
|                 "@fortawesome/fontawesome-free": "^6.7.2", | ||||
|                 "@goauthentik/api": "^2025.6.1-1749515784", | ||||
|                 "@goauthentik/api": "^2025.6.2-1750242193", | ||||
|                 "@lit/context": "^1.1.2", | ||||
|                 "@lit/localize": "^0.12.2", | ||||
|                 "@lit/reactive-element": "^2.0.4", | ||||
|                 "@lit/task": "^1.0.2", | ||||
|                 "@mdx-js/mdx": "^3.1.0", | ||||
|                 "@mrmarble/djangoql-completion": "^0.8.3", | ||||
|                 "@open-wc/lit-helpers": "^0.7.0", | ||||
|                 "@patternfly/elements": "^4.1.0", | ||||
|                 "@patternfly/patternfly": "^4.224.2", | ||||
|                 "@sentry/browser": "^9.29.0", | ||||
|                 "@sentry/browser": "^9.30.0", | ||||
|                 "@spotlightjs/spotlight": "^3.0.0", | ||||
|                 "@webcomponents/webcomponentsjs": "^2.8.0", | ||||
|                 "change-case": "^5.4.4", | ||||
| @ -123,7 +124,7 @@ | ||||
|                 "storybook-addon-mock": "^5.0.0", | ||||
|                 "turnstile-types": "^1.2.3", | ||||
|                 "typescript": "^5.8.3", | ||||
|                 "typescript-eslint": "^8.34.0", | ||||
|                 "typescript-eslint": "^8.34.1", | ||||
|                 "vite-plugin-lit-css": "^2.0.0", | ||||
|                 "vite-tsconfig-paths": "^5.0.1", | ||||
|                 "wireit": "^0.14.12" | ||||
| @ -1728,9 +1729,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@goauthentik/api": { | ||||
|             "version": "2025.6.1-1749515784", | ||||
|             "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.1-1749515784.tgz", | ||||
|             "integrity": "sha512-0yN4vJ2/grtNz6OVNMW34gd6TylBeyTSoH1Zlr7e2yeAbg+oZB8WmpLLCZGqvOguYGN6vYEYrPQF1k3RJohmlQ==" | ||||
|             "version": "2025.6.2-1750242193", | ||||
|             "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750242193.tgz", | ||||
|             "integrity": "sha512-yAi3LbxGP1pyu/lV+iiJp2FrIhOa9/nMErdiy4KC2uh63AS74iqNpYb4/IIbkCeT8fbSSNKhAbA6gXE9zFZ6Yg==" | ||||
|         }, | ||||
|         "node_modules/@goauthentik/core": { | ||||
|             "resolved": "packages/core", | ||||
| @ -2660,6 +2661,16 @@ | ||||
|                 "langium": "3.3.1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@mrmarble/djangoql-completion": { | ||||
|             "version": "0.8.3", | ||||
|             "resolved": "https://registry.npmjs.org/@mrmarble/djangoql-completion/-/djangoql-completion-0.8.3.tgz", | ||||
|             "integrity": "sha512-wYctvF0gQs48wL9jLQ+H2g2B0yJj7CrUSNi4ec5gcSuICIRqD/QSt6G+3zDdeW1LlI/4uj/FByJvg8k4TAAnVg==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "lex": "1.7.9", | ||||
|                 "lodash": "4.17.21" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@napi-rs/nice": { | ||||
|             "version": "1.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", | ||||
| @ -4479,75 +4490,75 @@ | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/@sentry-internal/browser-utils": { | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.29.0.tgz", | ||||
|             "integrity": "sha512-Wp6UJCDVV2KVK+TG8GwdLZyDy4GtUYDmVhGMpHKPS3G/Qgpf36cY/XHwChwaHZ5P9Bk1sjS9Ok698J59S8L2nw==", | ||||
|             "version": "9.30.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.30.0.tgz", | ||||
|             "integrity": "sha512-e6ZlN8oWheCB0YJSGlBNUlh6UPnY5Ecj1P+/cgeKBhNm7c3bIx4J50485hB8LQsu+b7Q11L2o/wucZ//Pb6FCg==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@sentry/core": "9.29.0" | ||||
|                 "@sentry/core": "9.30.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=18" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@sentry-internal/feedback": { | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.29.0.tgz", | ||||
|             "integrity": "sha512-ADvetGrtr+RfYcQKrQxah4fHs/xDJ/VjbStVMSuaNllzwWPYNkWIGFE6YjQ7wZszj0DQIu5/H+B6lZKsFYk4xw==", | ||||
|             "version": "9.30.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.30.0.tgz", | ||||
|             "integrity": "sha512-qAZ7xxLqZM7GlEvmSUmTHnoueg+fc7esMQD4vH8pS7HI3n9C5MjGn3HHlndRpD8lL7iUUQ0TPZQgU6McbzMDyw==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@sentry/core": "9.29.0" | ||||
|                 "@sentry/core": "9.30.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=18" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@sentry-internal/replay": { | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.29.0.tgz", | ||||
|             "integrity": "sha512-we/1JPRje8sNowQCyogOV1OYWuDOP/3XmDi48XoFG2HB0XMl2HfL5LI8AvgAvC/5nrqVAAo4ktbjoVLm1fb7rg==", | ||||
|             "version": "9.30.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.30.0.tgz", | ||||
|             "integrity": "sha512-+6wkqQGLJuFUzvGRzbh3iIhFGyxQx/Oxc0ODDKmz9ag2xYRjCYb3UUQXmQX9navAF0HXUsq8ajoJPm2L1ZyWVg==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@sentry-internal/browser-utils": "9.29.0", | ||||
|                 "@sentry/core": "9.29.0" | ||||
|                 "@sentry-internal/browser-utils": "9.30.0", | ||||
|                 "@sentry/core": "9.30.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=18" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@sentry-internal/replay-canvas": { | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.29.0.tgz", | ||||
|             "integrity": "sha512-TrQYhSAVPhyenvu0fNkon7BznFibu1mzS5bCudxhgOWajZluUVrXcbp8Q3WZ3R+AogrcgA3Vy6aumP/+fMKdwg==", | ||||
|             "version": "9.30.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.30.0.tgz", | ||||
|             "integrity": "sha512-I4MxS27rfV7vnOU29L80y4baZ4I1XqpnYvC/yLN7C17nA8eDCufQ8WVomli41y8JETnfcxlm68z7CS0sO4RCSA==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@sentry-internal/replay": "9.29.0", | ||||
|                 "@sentry/core": "9.29.0" | ||||
|                 "@sentry-internal/replay": "9.30.0", | ||||
|                 "@sentry/core": "9.30.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=18" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@sentry/browser": { | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.29.0.tgz", | ||||
|             "integrity": "sha512-+GFX/yb+rh6V1fSgTYM6ttAgledl2aUR3T3Rg86HNuegbdX8ym6lOtUOIZ0j9jPK015HR47KIPyIZVZZJ7Rj9g==", | ||||
|             "version": "9.30.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.30.0.tgz", | ||||
|             "integrity": "sha512-sRyW6A9nIieTTI26MYXk1DmWEhmphTjZevusNWla+vvUigCmSjuH+xZw19w43OyvF3bu261Skypnm/mAalOTwg==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@sentry-internal/browser-utils": "9.29.0", | ||||
|                 "@sentry-internal/feedback": "9.29.0", | ||||
|                 "@sentry-internal/replay": "9.29.0", | ||||
|                 "@sentry-internal/replay-canvas": "9.29.0", | ||||
|                 "@sentry/core": "9.29.0" | ||||
|                 "@sentry-internal/browser-utils": "9.30.0", | ||||
|                 "@sentry-internal/feedback": "9.30.0", | ||||
|                 "@sentry-internal/replay": "9.30.0", | ||||
|                 "@sentry-internal/replay-canvas": "9.30.0", | ||||
|                 "@sentry/core": "9.30.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=18" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@sentry/core": { | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.29.0.tgz", | ||||
|             "integrity": "sha512-wDyNe45PM+RCGtUn1tK7LzJ08ksv8i8KRUHrst7lsinEfRm83YH+wbWrPmwkVNEngUZvYkHwGLbNXM7xgFUuDQ==", | ||||
|             "version": "9.30.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.30.0.tgz", | ||||
|             "integrity": "sha512-JfEpeQ8a1qVJEb9DxpFTFy1J1gkNdlgKAPiqYGNnm4yQbnfl2Kb/iEo1if70FkiHc52H8fJwISEF90pzMm6lPg==", | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
|                 "node": ">=18" | ||||
| @ -7345,17 +7356,17 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", | ||||
|             "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", | ||||
|             "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/regexpp": "^4.10.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/type-utils": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/type-utils": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "graphemer": "^1.4.0", | ||||
|                 "ignore": "^7.0.0", | ||||
|                 "natural-compare": "^1.4.0", | ||||
| @ -7369,7 +7380,7 @@ | ||||
|                 "url": "https://opencollective.com/typescript-eslint" | ||||
|             }, | ||||
|             "peerDependencies": { | ||||
|                 "@typescript-eslint/parser": "^8.34.0", | ||||
|                 "@typescript-eslint/parser": "^8.34.1", | ||||
|                 "eslint": "^8.57.0 || ^9.0.0", | ||||
|                 "typescript": ">=4.8.4 <5.9.0" | ||||
|             } | ||||
| @ -7385,16 +7396,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/parser": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", | ||||
|             "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", | ||||
|             "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -7410,14 +7421,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/project-service": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", | ||||
|             "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", | ||||
|             "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.0", | ||||
|                 "@typescript-eslint/types": "^8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.1", | ||||
|                 "@typescript-eslint/types": "^8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -7432,14 +7443,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/scope-manager": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", | ||||
|             "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", | ||||
|             "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -7450,9 +7461,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/tsconfig-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -7467,14 +7478,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/type-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "ts-api-utils": "^2.1.0" | ||||
|             }, | ||||
| @ -7491,9 +7502,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/types": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", | ||||
|             "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -7505,16 +7516,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", | ||||
|             "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/project-service": "8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/project-service": "8.34.1", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "fast-glob": "^3.3.2", | ||||
|                 "is-glob": "^4.0.3", | ||||
| @ -7534,16 +7545,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.7.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0" | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -7558,14 +7569,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/visitor-keys": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", | ||||
|             "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", | ||||
|             "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "eslint-visitor-keys": "^4.2.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "eslint-visitor-keys": "^4.2.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -18978,6 +18989,12 @@ | ||||
|                 "node": ">= 0.8.0" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/lex": { | ||||
|             "version": "1.7.9", | ||||
|             "resolved": "https://registry.npmjs.org/lex/-/lex-1.7.9.tgz", | ||||
|             "integrity": "sha512-vzaalVBmFLnMaedq0QAsBAaXsWahzRpvnIBdBjj7y+7EKTS6lnziU2y/PsU2c6rV5qYj2B5IDw0uNJ9peXD0vw==", | ||||
|             "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." | ||||
|         }, | ||||
|         "node_modules/lie": { | ||||
|             "version": "3.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", | ||||
| @ -26933,15 +26950,15 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/typescript-eslint": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", | ||||
|             "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", | ||||
|             "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.0", | ||||
|                 "@typescript-eslint/parser": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0" | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.1", | ||||
|                 "@typescript-eslint/parser": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
|  | ||||
| @ -93,16 +93,17 @@ | ||||
|         "@floating-ui/dom": "^1.6.11", | ||||
|         "@formatjs/intl-listformat": "^7.7.11", | ||||
|         "@fortawesome/fontawesome-free": "^6.7.2", | ||||
|         "@goauthentik/api": "^2025.6.1-1749515784", | ||||
|         "@goauthentik/api": "^2025.6.2-1750242193", | ||||
|         "@lit/context": "^1.1.2", | ||||
|         "@lit/localize": "^0.12.2", | ||||
|         "@lit/reactive-element": "^2.0.4", | ||||
|         "@lit/task": "^1.0.2", | ||||
|         "@mdx-js/mdx": "^3.1.0", | ||||
|         "@mrmarble/djangoql-completion": "^0.8.3", | ||||
|         "@open-wc/lit-helpers": "^0.7.0", | ||||
|         "@patternfly/elements": "^4.1.0", | ||||
|         "@patternfly/patternfly": "^4.224.2", | ||||
|         "@sentry/browser": "^9.29.0", | ||||
|         "@sentry/browser": "^9.30.0", | ||||
|         "@spotlightjs/spotlight": "^3.0.0", | ||||
|         "@webcomponents/webcomponentsjs": "^2.8.0", | ||||
|         "change-case": "^5.4.4", | ||||
| @ -194,7 +195,7 @@ | ||||
|         "storybook-addon-mock": "^5.0.0", | ||||
|         "turnstile-types": "^1.2.3", | ||||
|         "typescript": "^5.8.3", | ||||
|         "typescript-eslint": "^8.34.0", | ||||
|         "typescript-eslint": "^8.34.1", | ||||
|         "vite-plugin-lit-css": "^2.0.0", | ||||
|         "vite-tsconfig-paths": "^5.0.1", | ||||
|         "wireit": "^0.14.12" | ||||
|  | ||||
							
								
								
									
										2
									
								
								web/packages/core/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/packages/core/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -14,7 +14,7 @@ declare module "module" { | ||||
|          * const relativeDirname = dirname(fileURLToPath(import.meta.url)); | ||||
|          * ``` | ||||
|          */ | ||||
|         // eslint-disable-next-line no-var | ||||
|  | ||||
|         var __dirname: string; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -88,7 +88,8 @@ export class RecentEventsCard extends Table<Event> { | ||||
|         } | ||||
|  | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Events found.")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No Events found.")}</span> | ||||
|                 <div slot="body">${msg("No matching events could be found.")}</div> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-file-input"; | ||||
| 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/components/ak-textarea-input"; | ||||
| @ -130,14 +131,14 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio | ||||
|                 required | ||||
|                 help=${msg("Application's display Name.")} | ||||
|             ></ak-text-input> | ||||
|             <ak-text-input | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 help=${msg("Internal application name used in URLs.")} | ||||
|                 input-hint="code" | ||||
|             ></ak-text-input> | ||||
|             ></ak-slug-input> | ||||
|             <ak-text-input | ||||
|                 name="group" | ||||
|                 value=${ifDefined(this.instance?.group)} | ||||
|  | ||||
| @ -117,13 +117,11 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep { | ||||
|                     ?invalid=${this.errors.has("name")} | ||||
|                     .errorMessages=${errors.name ?? this.errorMessages("name")} | ||||
|                     help=${msg("Application's display Name.")} | ||||
|                     id="ak-application-wizard-details-name" | ||||
|                 ></ak-text-input> | ||||
|                 <ak-slug-input | ||||
|                     name="slug" | ||||
|                     value=${ifDefined(app.slug)} | ||||
|                     label=${msg("Slug")} | ||||
|                     source="#ak-application-wizard-details-name" | ||||
|                     required | ||||
|                     ?invalid=${errors.slug ?? this.errors.has("slug")} | ||||
|                     .errorMessages=${this.errorMessages("slug")} | ||||
|  | ||||
| @ -115,7 +115,8 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep { | ||||
|                     .columns=${COLUMNS} | ||||
|                     .content=${[]} | ||||
|                 ></ak-select-table> | ||||
|                 <ak-empty-state header=${msg("No bound policies.")} icon="pf-icon-module"> | ||||
|                 <ak-empty-state icon="pf-icon-module" | ||||
|                     ><span slot="header">${msg("No bound policies.")} </span> | ||||
|                     <div slot="body">${msg("No policies are currently bound to this object.")}</div> | ||||
|                     <div slot="primary"> | ||||
|                         <button | ||||
|  | ||||
| @ -20,6 +20,7 @@ import { Event, EventsApi } from "@goauthentik/api"; | ||||
| @customElement("ak-event-list") | ||||
| export class EventListPage extends TablePage<Event> { | ||||
|     expandable = true; | ||||
|     supportsQL = true; | ||||
|  | ||||
|     pageTitle(): string { | ||||
|         return msg("Event Log"); | ||||
|  | ||||
| @ -135,7 +135,8 @@ export class BoundStagesList extends Table<FlowStageBinding> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Stages bound")} icon="pf-icon-module"> | ||||
|             html`<ak-empty-state icon="pf-icon-module"> | ||||
|                 <span slot="header">${msg("No Stages bound")}</span> | ||||
|                 <div slot="body">${msg("No stages are currently bound to this flow.")}</div> | ||||
|                 <div slot="primary"> | ||||
|                     <ak-stage-wizard | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util | ||||
| import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes"; | ||||
| import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | ||||
| @ -91,17 +92,16 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) { | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control pf-m-monospace" | ||||
|                     autocomplete="off" | ||||
|                     spellcheck="false" | ||||
|                     required | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text">${msg("Visible in the URL.")}</p> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 help=${msg("Visible in the URL.")} | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal label=${msg("Designation")} required name="designation"> | ||||
|                 <select class="pf-c-form-control"> | ||||
|                     <option value="" ?selected=${this.instance?.designation === undefined}> | ||||
|  | ||||
| @ -198,7 +198,8 @@ export class BoundPoliciesList extends Table<PolicyBinding> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Policies bound.")} icon="pf-icon-module"> | ||||
|             html`<ak-empty-state icon="pf-icon-module" | ||||
|                 ><span slot="header">${msg("No Policies bound.")}</span> | ||||
|                 <div slot="body">${msg("No policies are currently bound to this object.")}</div> | ||||
|                 <div slot="primary"> | ||||
|                     <ak-policy-wizard | ||||
|  | ||||
| @ -9,6 +9,7 @@ import { | ||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-secret-text-input.js"; | ||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/components/ak-switch-input"; | ||||
| import "@goauthentik/components/ak-text-input"; | ||||
| import "@goauthentik/components/ak-textarea-input"; | ||||
| @ -87,12 +88,13 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke | ||||
|                 value=${ifDefined(this.instance?.name)} | ||||
|                 required | ||||
|             ></ak-text-input> | ||||
|             <ak-text-input | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 label=${msg("Slug")} | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|             ></ak-text-input> | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|             <ak-switch-input | ||||
|                 name="enabled" | ||||
|                 ?checked=${this.instance?.enabled ?? true} | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { placeholderHelperText } from "@goauthentik/admin/helperText"; | ||||
| import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-secret-text-input.js"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| @ -54,14 +55,15 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> { | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -10,6 +10,7 @@ import { | ||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-radio-input"; | ||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/CodeMirror"; | ||||
| import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| @ -267,16 +268,13 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control pf-m-monospace" | ||||
|                     autocomplete="off" | ||||
|                     spellcheck="false" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -10,6 +10,7 @@ import { | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex"; | ||||
| import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js"; | ||||
| @ -183,14 +184,15 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -9,6 +9,7 @@ import { | ||||
|     UserMatchingModeToLabel, | ||||
| } from "@goauthentik/admin/sources/oauth/utils"; | ||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| @ -89,14 +90,15 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { placeholderHelperText } from "@goauthentik/admin/helperText"; | ||||
| import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| @ -48,14 +49,15 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> { | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <div class="pf-c-check"> | ||||
|                     <input | ||||
|  | ||||
| @ -41,14 +41,27 @@ export class InvitationForm extends ModelForm<Invitation, string> { | ||||
|     } | ||||
|  | ||||
|     renderForm(): TemplateResult { | ||||
|         return html` <ak-form-element-horizontal slugMode label=${msg("Name")} required name="name"> | ||||
|         const checkSlug = (ev: InputEvent) => { | ||||
|             if (ev && ev.target && ev.target instanceof HTMLInputElement) { | ||||
|                 ev.target.value = (ev.target.value ?? "").replace(/[^a-z0-9-]/g, ""); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return html` <ak-form-element-horizontal label=${msg("Name")} required name="name"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     id="admin-stages-invitation-name" | ||||
|                     value="${this.instance?.name || ""}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                     @input=${(ev: InputEvent) => checkSlug(ev)} | ||||
|                     data-ak-slug="true" | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     ${msg( | ||||
|                         "The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.", | ||||
|                     )} | ||||
|                 </p> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Expires")} required name="expires"> | ||||
|                 <input | ||||
|  | ||||
| @ -85,6 +85,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa | ||||
|     expandable = true; | ||||
|     checkbox = true; | ||||
|     clearOnRefresh = true; | ||||
|     supportsQL = true; | ||||
|  | ||||
|     searchEnabled(): boolean { | ||||
|         return true; | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { me } from "@goauthentik/common/users.js"; | ||||
| import { isUserRoute } from "@goauthentik/elements/router/utils.js"; | ||||
| import { deepmerge, deepmergeInto } from "deepmerge-ts"; | ||||
|  | ||||
| import { UiThemeEnum, UserSelf } from "@goauthentik/api"; | ||||
| import { CurrentBrand } from "@goauthentik/api"; | ||||
| @ -96,13 +97,12 @@ export class DefaultUIConfig implements UIConfig { | ||||
| let globalUiConfig: Promise<UIConfig>; | ||||
|  | ||||
| export function getConfigForUser(user: UserSelf): UIConfig { | ||||
|     const settings = user.settings; | ||||
|     let config = new DefaultUIConfig(); | ||||
|     const settings = user.settings as UIConfig; | ||||
|     const config = new DefaultUIConfig(); | ||||
|     if (!settings) { | ||||
|         return config; | ||||
|     } | ||||
|     config = Object.assign(new DefaultUIConfig(), settings); | ||||
|     return config; | ||||
|     return deepmerge({ ...config }, settings); | ||||
| } | ||||
|  | ||||
| export function uiConfig(): Promise<UIConfig> { | ||||
|  | ||||
							
								
								
									
										306
									
								
								web/src/components/ak-search-ql/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								web/src/components/ak-search-ql/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,306 @@ | ||||
| import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/buttons/Dropdown"; | ||||
| import { PaginatedResponse } from "@goauthentik/elements/table/Table"; | ||||
| import DjangoQL, { Introspections } from "@mrmarble/djangoql-completion"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { CSSResult, TemplateResult, css, html, nothing } from "lit"; | ||||
| import { customElement, property, query, state } from "lit/decorators.js"; | ||||
| import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; | ||||
| import PFSearchInput from "@patternfly/patternfly/components/SearchInput/search-input.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
|  | ||||
| export class QL extends DjangoQL { | ||||
|     createCompletionElement() { | ||||
|         this.completionEnabled = !!this.options.completionEnabled; | ||||
|         return; | ||||
|     } | ||||
|     logError(message: string): void { | ||||
|         console.warn(`authentik/ql: ${message}`); | ||||
|     } | ||||
|     textareaResize() {} | ||||
| } | ||||
|  | ||||
| @customElement("ak-search-ql") | ||||
| export class QLSearch extends AKElement { | ||||
|     @property() | ||||
|     value?: string; | ||||
|  | ||||
|     @query("[name=search]") | ||||
|     searchElement?: HTMLTextAreaElement; | ||||
|  | ||||
|     @state() | ||||
|     menuOpen = false; | ||||
|  | ||||
|     @property() | ||||
|     onSearch?: (value: string) => void; | ||||
|  | ||||
|     @state() | ||||
|     selected?: number; | ||||
|  | ||||
|     @state() | ||||
|     cursorX: number = 0; | ||||
|  | ||||
|     @state() | ||||
|     cursorY: number = 0; | ||||
|  | ||||
|     ql?: QL; | ||||
|     canvas?: CanvasRenderingContext2D; | ||||
|  | ||||
|     set apiResponse(value: PaginatedResponse<unknown> | undefined) { | ||||
|         if (!value || !value.autocomplete || !this.ql) { | ||||
|             return; | ||||
|         } | ||||
|         this.ql.loadIntrospections(value.autocomplete as unknown as Introspections); | ||||
|     } | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [ | ||||
|             PFBase, | ||||
|             PFFormControl, | ||||
|             PFSearchInput, | ||||
|             css` | ||||
|                 ::-webkit-search-cancel-button { | ||||
|                     display: none; | ||||
|                 } | ||||
|                 .ql.pf-c-form-control { | ||||
|                     font-family: monospace; | ||||
|                     resize: vertical; | ||||
|                     height: 2.25em; | ||||
|                 } | ||||
|                 .selected { | ||||
|                     background-color: var(--pf-c-search-input__menu-item--hover--BackgroundColor); | ||||
|                 } | ||||
|                 :host([theme="dark"]) .pf-c-search-input__menu { | ||||
|                     --pf-c-search-input__menu--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
|                     color: var(--ak-dark-foreground); | ||||
|                 } | ||||
|                 :host([theme="dark"]) .pf-c-search-input__menu-item { | ||||
|                     --pf-c-search-input__menu-item--Color: var(--ak-dark-foreground); | ||||
|                 } | ||||
|                 :host([theme="dark"]) .pf-c-search-input__menu-item:hover { | ||||
|                     --pf-c-search-input__menu-item--BackgroundColor: var( | ||||
|                         --ak-dark-background-lighter | ||||
|                     ); | ||||
|                 } | ||||
|                 :host([theme="dark"]) .pf-c-search-input__menu-list-item.selected { | ||||
|                     --pf-c-search-input__menu-item--hover--BackgroundColor: var( | ||||
|                         --ak-dark-background-light | ||||
|                     ); | ||||
|                 } | ||||
|                 :host([theme="dark"]) .pf-c-search-input__text::before { | ||||
|                     border: 0; | ||||
|                 } | ||||
|                 .pf-c-search-input__menu { | ||||
|                     position: fixed; | ||||
|                     min-width: 0; | ||||
|                 } | ||||
|             `, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     firstUpdated() { | ||||
|         if (!this.searchElement) { | ||||
|             return; | ||||
|         } | ||||
|         this.ql = new QL({ | ||||
|             completionEnabled: true, | ||||
|             introspections: { | ||||
|                 current_model: "", | ||||
|                 models: {}, | ||||
|             }, | ||||
|             selector: this.searchElement, | ||||
|             autoResize: false, | ||||
|         }); | ||||
|         const canvas = document.createElement("canvas"); | ||||
|         const context = canvas.getContext("2d"); | ||||
|         if (!context) { | ||||
|             console.error("authentik/ql: failed to get canvas context"); | ||||
|             return; | ||||
|         } | ||||
|         context.font = window.getComputedStyle(this.searchElement).font; | ||||
|         this.canvas = context; | ||||
|     } | ||||
|  | ||||
|     refreshCompletions() { | ||||
|         this.value = this.searchElement?.value; | ||||
|         if (!this.ql) { | ||||
|             return; | ||||
|         } | ||||
|         this.ql.generateSuggestions(); | ||||
|         if (this.ql.suggestions.length < 1 || this.ql.loading) { | ||||
|             this.menuOpen = false; | ||||
|             return; | ||||
|         } | ||||
|         this.menuOpen = true; | ||||
|         this.updateDropdownPosition(); | ||||
|         this.requestUpdate(); | ||||
|     } | ||||
|  | ||||
|     updateDropdownPosition() { | ||||
|         if (!this.searchElement) { | ||||
|             return; | ||||
|         } | ||||
|         const bcr = this.getBoundingClientRect(); | ||||
|         // We need the width of a letter to measure x; we use a monospaced font but still | ||||
|         // check the length for `m` as its the widest ASCII char | ||||
|         const metrics = this.canvas?.measureText("m"); | ||||
|         const letterWidth = Math.ceil(metrics?.width || 0) + 1; | ||||
|  | ||||
|         // Mostly static variables for padding, font line-height and how many | ||||
|         const lineHeight = parseInt(window.getComputedStyle(this.searchElement).lineHeight, 10); | ||||
|         const paddingTop = parseInt(window.getComputedStyle(this.searchElement).paddingTop, 10); | ||||
|         const paddingLeft = parseInt(window.getComputedStyle(this.searchElement).paddingLeft, 10); | ||||
|         const paddingRight = parseInt(window.getComputedStyle(this.searchElement).paddingRight, 10); | ||||
|         const actualInnerWidth = bcr.width - paddingLeft - paddingRight; | ||||
|  | ||||
|         let relX = 0; | ||||
|         let relY = 1; | ||||
|         let letterIndex = 0; | ||||
|  | ||||
|         this.searchElement.value.split(" ").some((word, idx) => { | ||||
|             letterIndex += word.length; | ||||
|             const newRelX = relX + word.length * letterWidth; | ||||
|             if (newRelX > actualInnerWidth) { | ||||
|                 relY += 1; | ||||
|                 if (letterIndex > this.searchElement!.selectionStart) { | ||||
|                     relX = | ||||
|                         letterWidth * word.length - | ||||
|                         (letterIndex - this.searchElement!.selectionStart) * letterWidth; | ||||
|                     return true; | ||||
|                 } | ||||
|                 relX = word.length * letterWidth; | ||||
|             } else { | ||||
|                 relX = newRelX + 1; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         this.cursorX = bcr.x + paddingLeft + relX; | ||||
|         this.cursorY = bcr.y + paddingTop + relY * lineHeight; | ||||
|     } | ||||
|  | ||||
|     onKeyDown(ev: KeyboardEvent) { | ||||
|         this.updateDropdownPosition(); | ||||
|         if (ev.key === "Enter" && ev.metaKey && this.onSearch && this.searchElement) { | ||||
|             this.onSearch(this.searchElement?.value); | ||||
|             return; | ||||
|         } | ||||
|         if (!this.menuOpen) return; | ||||
|         switch (ev.key) { | ||||
|             case "ArrowUp": | ||||
|                 if (this.ql?.suggestions.length) { | ||||
|                     if (this.selected === undefined) { | ||||
|                         this.selected = this.ql?.suggestions.length - 1; | ||||
|                     } else if (this.selected === 0) { | ||||
|                         this.selected = undefined; | ||||
|                     } else { | ||||
|                         this.selected -= 1; | ||||
|                     } | ||||
|                     this.refreshCompletions(); | ||||
|                     ev.preventDefault(); | ||||
|                 } | ||||
|                 break; | ||||
|             case "ArrowDown": | ||||
|                 if (this.ql?.suggestions.length) { | ||||
|                     if (this.selected === undefined) { | ||||
|                         this.selected = 0; | ||||
|                     } else if (this.selected < this.ql?.suggestions.length - 1) { | ||||
|                         this.selected += 1; | ||||
|                     } else { | ||||
|                         this.selected = undefined; | ||||
|                     } | ||||
|                     this.refreshCompletions(); | ||||
|                     ev.preventDefault(); | ||||
|                 } | ||||
|                 break; | ||||
|             case "Tab": | ||||
|                 if (this.selected) { | ||||
|                     this.ql?.selectCompletion(this.selected); | ||||
|                     ev.preventDefault(); | ||||
|                 } | ||||
|                 break; | ||||
|             case "Enter": | ||||
|                 // Technically this is a textarea, due to automatic multi-line feature, | ||||
|                 // but other than that it should look and behave like a normal input. | ||||
|                 // So expected behavior when pressing Enter is to submit the form, | ||||
|                 // not to add a new line. | ||||
|                 if (this.selected !== undefined) { | ||||
|                     this.ql?.selectCompletion(this.selected); | ||||
|                 } | ||||
|                 ev.preventDefault(); | ||||
|                 break; | ||||
|             case "Escape": | ||||
|                 this.menuOpen = false; | ||||
|                 break; | ||||
|             case "Shift": // Shift | ||||
|             case "Control": // Ctrl | ||||
|             case "Alt": // Alt | ||||
|             case "Meta": // Windows Key or Cmd on Mac | ||||
|                 // Control keys shouldn't trigger completion popup | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     renderMenu() { | ||||
|         if (!this.menuOpen || !this.ql) { | ||||
|             return nothing; | ||||
|         } | ||||
|         return html` | ||||
|             <div | ||||
|                 class="pf-c-search-input__menu" | ||||
|                 style="left: ${this.cursorX}px; top: ${this.cursorY}px;" | ||||
|             > | ||||
|                 <ul class="pf-c-search-input__menu-list"> | ||||
|                     ${this.ql.suggestions.map((suggestion, idx) => { | ||||
|                         return html`<li | ||||
|                             class="pf-c-search-input__menu-list-item ${this.selected === idx | ||||
|                                 ? "selected" | ||||
|                                 : ""}" | ||||
|                         > | ||||
|                             <button | ||||
|                                 class="pf-c-search-input__menu-item" | ||||
|                                 type="button" | ||||
|                                 @click=${() => { | ||||
|                                     this.ql?.selectCompletion(idx); | ||||
|                                     this.refreshCompletions(); | ||||
|                                 }} | ||||
|                             > | ||||
|                                 <span class="pf-c-search-input__menu-item-text" | ||||
|                                     >${suggestion.text}</span | ||||
|                                 > | ||||
|                             </button> | ||||
|                         </li>`; | ||||
|                     })} | ||||
|                 </ul> | ||||
|             </div> | ||||
|         `; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-c-search-input"> | ||||
|             <div class="pf-c-search-input__bar"> | ||||
|                 <span class="pf-c-search-input__text"> | ||||
|                     <textarea | ||||
|                         class="pf-c-form-control ql" | ||||
|                         name="search" | ||||
|                         placeholder=${msg("Search...")} | ||||
|                         spellcheck="false" | ||||
|                         @input=${(ev: InputEvent) => this.refreshCompletions()} | ||||
|                         @keydown=${this.onKeyDown} | ||||
|                     > | ||||
| ${ifDefined(this.value)}</textarea | ||||
|                     > | ||||
|                 </span> | ||||
|             </div> | ||||
|             ${this.renderMenu()} | ||||
|         </div>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|     interface HTMLElementTagNameMap { | ||||
|         "ak-search-ql": QLSearch; | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,5 @@ | ||||
| import { formatSlug } from "@goauthentik/elements/router/utils.js"; | ||||
| import { bound } from "@goauthentik/elements/decorators/bound.js"; | ||||
| import { kebabCase } from "change-case"; | ||||
|  | ||||
| import { html } from "lit"; | ||||
| import { customElement, property, query } from "lit/decorators.js"; | ||||
| @ -6,59 +7,83 @@ import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import { HorizontalLightComponent } from "./HorizontalLightComponent"; | ||||
|  | ||||
| const slugify = (s: string) => kebabCase(s, { suffixCharacters: "-" }); | ||||
|  | ||||
| /** | ||||
|  * @element ak-slug-input | ||||
|  * @class AkSlugInput | ||||
|  * | ||||
|  * A wrapper around `ak-form-element-horizontal` and a text input control that listens for input on | ||||
|  * a peer text input control and automatically mirrors that control's value, transforming the value | ||||
|  * into a slug and displaying it separately. | ||||
|  * | ||||
|  * If the user manually changes the slug, mirroring and transformation stop. If, after that, both | ||||
|  * fields are cleared manually, mirroring and transformation resume. | ||||
|  * | ||||
|  * ## Limitations: | ||||
|  * | ||||
|  * Both the source text field and the slug field must be rendered in the same render pass (i.e., | ||||
|  * part of the same singular call to a `render` function) so that the slug field can find its | ||||
|  * source. | ||||
|  * | ||||
|  * For the same reason, both the source text field and the slug field must share the same immediate | ||||
|  * parent DOM object. | ||||
|  * | ||||
|  * Since we expect the source text field and the slug to be part of the same form and rendered not | ||||
|  * just in the same form but in the same form group, these are not considered burdensome | ||||
|  * restrictions. | ||||
|  */ | ||||
| @customElement("ak-slug-input") | ||||
| export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|     @property({ type: String, reflect: true }) | ||||
|     value = ""; | ||||
|  | ||||
|     /** | ||||
|      * A selector indicating the source text input control. Must be unique within the whole DOM | ||||
|      * context of the slug and source controls. The most common use in authentik is the default: | ||||
|      * slugifying the "name" of something. | ||||
|      */ | ||||
|     @property({ type: String }) | ||||
|     source = ""; | ||||
|     public source = "[name='name']"; | ||||
|  | ||||
|     origin?: HTMLInputElement | null; | ||||
|     @property({ type: String, reflect: true }) | ||||
|     public value = ""; | ||||
|  | ||||
|     @query("input") | ||||
|     input!: HTMLInputElement; | ||||
|     private input!: HTMLInputElement; | ||||
|  | ||||
|     touched: boolean = false; | ||||
|     #origin?: HTMLInputElement | null; | ||||
|  | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this.slugify = this.slugify.bind(this); | ||||
|         this.handleTouch = this.handleTouch.bind(this); | ||||
|     } | ||||
|  | ||||
|     firstUpdated() { | ||||
|         this.input.addEventListener("input", this.handleTouch); | ||||
|     } | ||||
|     #touched: boolean = false; | ||||
|  | ||||
|     // Do not stop propagation of this event; it must be sent up the tree so that a parent | ||||
|     // component, such as a custom forms manager, may receive it. | ||||
|     handleTouch(ev: Event) { | ||||
|         this.input.value = formatSlug(this.input.value); | ||||
|         this.value = this.input.value; | ||||
|     protected handleTouch(ev: Event) { | ||||
|         this.value = this.input.value = slugify(this.input.value); | ||||
|  | ||||
|         if (this.origin && this.origin.value === "" && this.input.value === "") { | ||||
|             this.touched = false; | ||||
|         // Reset 'touched' status if the slug & target have been reset | ||||
|         if (this.#origin && this.#origin.value === "" && this.input.value === "") { | ||||
|             this.#touched = false; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (ev && ev.target && ev.target instanceof HTMLInputElement) { | ||||
|             this.touched = true; | ||||
|             this.#touched = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     slugify(ev: Event) { | ||||
|     @bound | ||||
|     protected slugify(ev: Event) { | ||||
|         if (!(ev && ev.target && ev.target instanceof HTMLInputElement)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Reset 'touched' status if the slug & target have been reset | ||||
|         if (ev.target.value === "" && this.input.value === "") { | ||||
|             this.touched = false; | ||||
|             this.#touched = false; | ||||
|         } | ||||
|  | ||||
|         // Don't proceed if the user has hand-modified the slug | ||||
|         if (this.touched) { | ||||
|         // Don't proceed if the user has hand-modified the slug. (Note the order of statements: if | ||||
|         // the user hand modified the slug to be empty as part of resetting the slug/source | ||||
|         // relationship, that's a "not-touched" condition and falls through.) | ||||
|         if (this.#touched) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @ -67,7 +92,7 @@ export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|         // "any event which adds or removes a character but leaves the rest of the slug looking like | ||||
|         // the previous iteration, set it to the current iteration." | ||||
|  | ||||
|         const newSlug = formatSlug(ev.target.value); | ||||
|         const newSlug = slugify(ev.target.value); | ||||
|         const oldSlug = this.input.value; | ||||
|         const [shorter, longer] = | ||||
|             newSlug.length < oldSlug.length ? [newSlug, oldSlug] : [oldSlug, newSlug]; | ||||
| @ -81,7 +106,6 @@ export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|         // to listeners, both the name and value of the host must match those of the target | ||||
|         // input. The name is already handled since it's both required and automatically | ||||
|         // forwarded to our templated input, but the value must also be set. | ||||
|  | ||||
|         this.value = this.input.value = newSlug; | ||||
|         this.dispatchEvent( | ||||
|             new Event("input", { | ||||
| @ -91,38 +115,36 @@ export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     connectedCallback() { | ||||
|         super.connectedCallback(); | ||||
|  | ||||
|         // Set up listener on source element, so we can slugify the content. | ||||
|         setTimeout(() => { | ||||
|             if (this.source) { | ||||
|                 const rootNode = this.getRootNode(); | ||||
|                 if (rootNode instanceof ShadowRoot || rootNode instanceof Document) { | ||||
|                     this.origin = rootNode.querySelector(this.source); | ||||
|                 } | ||||
|                 if (this.origin) { | ||||
|                     this.origin.addEventListener("input", this.slugify); | ||||
|                 } | ||||
|             } | ||||
|         }, 0); | ||||
|     } | ||||
|  | ||||
|     disconnectedCallback() { | ||||
|         if (this.origin) { | ||||
|             this.origin.removeEventListener("input", this.slugify); | ||||
|     public override disconnectedCallback() { | ||||
|         if (this.#origin) { | ||||
|             this.#origin.removeEventListener("input", this.slugify); | ||||
|         } | ||||
|         super.disconnectedCallback(); | ||||
|     } | ||||
|  | ||||
|     renderControl() { | ||||
|     public override renderControl() { | ||||
|         return html`<input | ||||
|             @input=${(ev: Event) => this.handleTouch(ev)} | ||||
|             type="text" | ||||
|             value=${ifDefined(this.value)} | ||||
|             class="pf-c-form-control" | ||||
|             ?required=${this.required} | ||||
|         />`; | ||||
|     } | ||||
|  | ||||
|     public override firstUpdated() { | ||||
|         if (!this.source) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const rootNode = this.getRootNode(); | ||||
|         if (rootNode instanceof ShadowRoot || rootNode instanceof Document) { | ||||
|             this.#origin = rootNode.querySelector(this.source); | ||||
|         } | ||||
|         if (this.#origin) { | ||||
|             this.#origin.addEventListener("input", this.slugify); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default AkSlugInput; | ||||
|  | ||||
| @ -94,7 +94,8 @@ export class ObjectChangelog extends Table<Event> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Events found.")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No Events found.")}</span> | ||||
|                 <div slot="body">${msg("No matching events could be found.")}</div> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
| @ -66,7 +66,8 @@ export class UserEvents extends Table<Event> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Events found.")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No Events found.")}</span> | ||||
|                 <div slot="body">${msg("No matching events could be found.")}</div> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/Spinner"; | ||||
| import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types"; | ||||
| import { spread } from "@open-wc/lit-helpers"; | ||||
| import { SlotController } from "@patternfly/pfe-core/controllers/slot-controller.js"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { css, html, nothing } from "lit"; | ||||
| @ -33,6 +34,8 @@ export class EmptyState extends AKElement implements IEmptyState { | ||||
|     @property() | ||||
|     header?: string; | ||||
|  | ||||
|     slots = new SlotController(this, "header", "body", "primary"); | ||||
|  | ||||
|     static get styles() { | ||||
|         return [ | ||||
|             PFBase, | ||||
| @ -48,6 +51,12 @@ export class EmptyState extends AKElement implements IEmptyState { | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         const showHeader = this.loading || this.slots.hasSlotted("header"); | ||||
|         const header = () => | ||||
|             this.slots.hasSlotted("header") | ||||
|                 ? html`<slot name="header"></slot>` | ||||
|                 : html`<span>${msg("Loading")}</span>`; | ||||
|  | ||||
|         return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}"> | ||||
|             <div class="pf-c-empty-state__content"> | ||||
|                 ${this.loading | ||||
| @ -59,15 +68,17 @@ export class EmptyState extends AKElement implements IEmptyState { | ||||
|                           "fa-question-circle"} pf-c-empty-state__icon" | ||||
|                           aria-hidden="true" | ||||
|                       ></i>`} | ||||
|                 <h1 class="pf-c-title pf-m-lg"> | ||||
|                     ${this.loading && this.header === undefined ? msg("Loading") : this.header} | ||||
|                 </h1> | ||||
|                 <div class="pf-c-empty-state__body"> | ||||
|                     <slot name="body"></slot> | ||||
|                 </div> | ||||
|                 <div class="pf-c-empty-state__primary"> | ||||
|                     <slot name="primary"></slot> | ||||
|                 </div> | ||||
|                 ${showHeader ? html` <h1 class="pf-c-title pf-m-lg">${header()}</h1>` : nothing} | ||||
|                 ${this.slots.hasSlotted("body") | ||||
|                     ? html` <div class="pf-c-empty-state__body"> | ||||
|                           <slot name="body"></slot> | ||||
|                       </div>` | ||||
|                     : nothing} | ||||
|                 ${this.slots.hasSlotted("primary") | ||||
|                     ? html` <div class="pf-c-empty-state__primary"> | ||||
|                           <slot name="primary"></slot> | ||||
|                       </div>` | ||||
|                     : nothing} | ||||
|             </div> | ||||
|         </div>`; | ||||
|     } | ||||
|  | ||||
| @ -200,7 +200,8 @@ export abstract class AKChart<T> extends AKElement { | ||||
|             <div class="container"> | ||||
|                 ${this.error | ||||
|                     ? html` | ||||
|                           <ak-empty-state header="${msg("Failed to fetch data.")}" icon="fa-times"> | ||||
|                           <ak-empty-state icon="fa-times" | ||||
|                               ><span slot="header">${msg("Failed to fetch data.")}</span> | ||||
|                               <p slot="body">${pluckErrorDetail(this.error)}</p> | ||||
|                           </ak-empty-state> | ||||
|                       ` | ||||
|  | ||||
| @ -40,7 +40,9 @@ export class LogViewer extends Table<LogEvent> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`, | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No log messages.")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -7,7 +7,6 @@ import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers"; | ||||
| import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; | ||||
| import { formatSlug } from "@goauthentik/elements/router/utils.js"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { CSSResult, TemplateResult, css, html } from "lit"; | ||||
| @ -197,39 +196,6 @@ export abstract class Form<T> extends AKElement { | ||||
|         return this.successMessage; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * After rendering the form, if there is both a `name` and `slug` element within the form, | ||||
|      * events the `name` element so that the slug will always have a slugified version of the | ||||
|      * `name.`. This duplicates functionality within ak-form-element-horizontal. | ||||
|      */ | ||||
|     updated(): void { | ||||
|         this.shadowRoot | ||||
|             ?.querySelectorAll("ak-form-element-horizontal[name=name]") | ||||
|             .forEach((nameInput) => { | ||||
|                 const input = nameInput.firstElementChild as HTMLInputElement; | ||||
|                 const form = nameInput.closest("form"); | ||||
|                 if (form === null) { | ||||
|                     return; | ||||
|                 } | ||||
|                 const slugFieldWrapper = form.querySelector( | ||||
|                     "ak-form-element-horizontal[name=slug]", | ||||
|                 ); | ||||
|                 if (!slugFieldWrapper) { | ||||
|                     return; | ||||
|                 } | ||||
|                 const slugField = slugFieldWrapper.firstElementChild as HTMLInputElement; | ||||
|                 // Only attach handler if the slug is already equal to the name | ||||
|                 // if not, they are probably completely different and shouldn't update | ||||
|                 // each other | ||||
|                 if (formatSlug(input.value) !== slugField.value) { | ||||
|                     return; | ||||
|                 } | ||||
|                 nameInput.addEventListener("input", () => { | ||||
|                     slugField.value = formatSlug(input.value); | ||||
|                 }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     resetForm(): void { | ||||
|         const form = this.shadowRoot?.querySelector<HTMLFormElement>("form"); | ||||
|         form?.reset(); | ||||
|  | ||||
| @ -77,9 +77,6 @@ export class HorizontalFormElement extends AKElement { | ||||
|     @property({ attribute: false }) | ||||
|     errorMessages: string[] | string[][] = []; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     slugMode = false; | ||||
|  | ||||
|     _invalid = false; | ||||
|  | ||||
|     /* If this property changes, we want to make sure the parent control is "opened" so | ||||
| @ -109,13 +106,6 @@ export class HorizontalFormElement extends AKElement { | ||||
|         this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { | ||||
|             input.focus(); | ||||
|         }); | ||||
|         if (this.name === "slug" || this.slugMode) { | ||||
|             this.querySelectorAll<HTMLInputElement>("input[type='text']").forEach((input) => { | ||||
|                 input.addEventListener("keyup", () => { | ||||
|                     input.value = formatSlug(input.value); | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
|         this.querySelectorAll("*").forEach((input) => { | ||||
|             if (isAkControl(input) && !input.getAttribute("name")) { | ||||
|                 input.setAttribute("name", this.name); | ||||
|  | ||||
| @ -163,7 +163,8 @@ export class NotificationDrawer extends AKElement { | ||||
|     } | ||||
|  | ||||
|     renderEmpty() { | ||||
|         return html`<ak-empty-state header=${msg("No notifications found.")}> | ||||
|         return html`<ak-empty-state | ||||
|             ><span slot="header">${msg("No notifications found.")}</span> | ||||
|             <div slot="body">${msg("You don't have any notifications currently.")}</div> | ||||
|         </ak-empty-state>`; | ||||
|     } | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import { WithLicenseSummary } from "#elements/mixins/license"; | ||||
| import { EVENT_REFRESH } from "@goauthentik/common/constants"; | ||||
| import { | ||||
|     APIError, | ||||
| @ -31,13 +32,20 @@ import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css"; | ||||
| import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
|  | ||||
| import { Pagination } from "@goauthentik/api"; | ||||
| import { LicenseSummaryStatusEnum, Pagination } from "@goauthentik/api"; | ||||
|  | ||||
| export interface TableLike { | ||||
|     order?: string; | ||||
|     fetch: () => void; | ||||
| } | ||||
|  | ||||
| export interface PaginatedResponse<T> { | ||||
|     pagination: Pagination; | ||||
|     autocomplete?: { [key: string]: string }; | ||||
|  | ||||
|     results: Array<T>; | ||||
| } | ||||
|  | ||||
| export class TableColumn { | ||||
|     title: string; | ||||
|     orderBy?: string; | ||||
| @ -94,19 +102,16 @@ export class TableColumn { | ||||
|     } | ||||
| } | ||||
|  | ||||
| export interface PaginatedResponse<T> { | ||||
|     pagination: Pagination; | ||||
|  | ||||
|     results: Array<T>; | ||||
| } | ||||
|  | ||||
| export abstract class Table<T> extends AKElement implements TableLike { | ||||
| export abstract class Table<T> extends WithLicenseSummary(AKElement) implements TableLike { | ||||
|     abstract apiEndpoint(): Promise<PaginatedResponse<T>>; | ||||
|     abstract columns(): TableColumn[]; | ||||
|     abstract row(item: T): SlottedTemplateResult[]; | ||||
|  | ||||
|     private isLoading = false; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     supportsQL: boolean = false; | ||||
|  | ||||
|     searchEnabled(): boolean { | ||||
|         return false; | ||||
|     } | ||||
| @ -181,6 +186,12 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|             PFDropdown, | ||||
|             PFPagination, | ||||
|             css` | ||||
|                 .pf-c-toolbar__group.pf-m-search-filter.ql { | ||||
|                     flex-grow: 1; | ||||
|                 } | ||||
|                 ak-table-search.ql { | ||||
|                     width: 100% !important; | ||||
|                 } | ||||
|                 .pf-c-table thead .pf-c-table__check { | ||||
|                     min-width: 3rem; | ||||
|                 } | ||||
| @ -288,7 +299,9 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|         return html`<tr role="row"> | ||||
|             <td role="cell" colspan="25"> | ||||
|                 <div class="pf-l-bullseye"> | ||||
|                     <ak-empty-state loading header=${msg("Loading")}></ak-empty-state> | ||||
|                     <ak-empty-state loading | ||||
|                         ><span slot="header">${msg("Loading")}</span></ak-empty-state | ||||
|                     > | ||||
|                 </div> | ||||
|             </td> | ||||
|         </tr>`; | ||||
| @ -300,8 +313,9 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|                 <td role="cell" colspan="8"> | ||||
|                     <div class="pf-l-bullseye"> | ||||
|                         ${inner ?? | ||||
|                         html`<ak-empty-state header="${msg("No objects found.")}" | ||||
|                             ><div slot="primary">${this.renderObjectCreate()}</div> | ||||
|                         html`<ak-empty-state | ||||
|                             ><span slot="header">${msg("No objects found.")}</span> > | ||||
|                             <div slot="primary">${this.renderObjectCreate()}</div> | ||||
|                         </ak-empty-state>`} | ||||
|                     </div> | ||||
|                 </td> | ||||
| @ -316,7 +330,8 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|     renderError(): SlottedTemplateResult { | ||||
|         if (!this.error) return nothing; | ||||
|  | ||||
|         return html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-ban"> | ||||
|         return html`<ak-empty-state icon="fa-ban" | ||||
|             ><span slot="header">${msg("Failed to fetch objects.")}</span> | ||||
|             <div slot="body">${pluckErrorDetail(this.error)}</div> | ||||
|         </ak-empty-state>`; | ||||
|     } | ||||
| @ -470,14 +485,17 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|             }); | ||||
|             this.fetch(); | ||||
|         }; | ||||
|  | ||||
|         const isQL = | ||||
|             this.supportsQL && this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed; | ||||
|         return !this.searchEnabled() | ||||
|             ? html`` | ||||
|             : html`<div class="pf-c-toolbar__group pf-m-search-filter"> | ||||
|             : html`<div class="pf-c-toolbar__group pf-m-search-filter ${isQL ? "ql" : ""}"> | ||||
|                   <ak-table-search | ||||
|                       class="pf-c-toolbar__item pf-m-search-filter" | ||||
|                       ?supportsQL=${this.supportsQL} | ||||
|                       class="pf-c-toolbar__item pf-m-search-filter ${isQL ? "ql" : ""}" | ||||
|                       value=${ifDefined(this.search)} | ||||
|                       .onSearch=${runSearch} | ||||
|                       .apiResponse=${this.data} | ||||
|                   > | ||||
|                   </ak-table-search> | ||||
|               </div>`; | ||||
|  | ||||
| @ -1,4 +1,7 @@ | ||||
| import { WithLicenseSummary } from "#elements/mixins/license"; | ||||
| import "@goauthentik/components/ak-search-ql"; | ||||
| import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import { PaginatedResponse } from "@goauthentik/elements/table/Table"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { CSSResult, TemplateResult, css, html } from "lit"; | ||||
| @ -11,11 +14,19 @@ import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-gro | ||||
| import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
|  | ||||
| import { LicenseSummaryStatusEnum } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-table-search") | ||||
| export class TableSearch extends AKElement { | ||||
| export class TableSearch extends WithLicenseSummary(AKElement) { | ||||
|     @property() | ||||
|     value?: string; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     supportsQL: boolean = false; | ||||
|  | ||||
|     @property({ attribute: false }) | ||||
|     apiResponse?: PaginatedResponse<unknown>; | ||||
|  | ||||
|     @property() | ||||
|     onSearch?: (value: string) => void; | ||||
|  | ||||
| @ -30,39 +41,63 @@ export class TableSearch extends AKElement { | ||||
|                 ::-webkit-search-cancel-button { | ||||
|                     display: none; | ||||
|                 } | ||||
|                 ak-search-ql { | ||||
|                     width: 100%; | ||||
|                 } | ||||
|             `, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     renderInput(): TemplateResult { | ||||
|         if ( | ||||
|             this.supportsQL && | ||||
|             this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed | ||||
|         ) { | ||||
|             return html`<ak-search-ql | ||||
|                 .apiResponse=${this.apiResponse} | ||||
|                 .value=${this.value} | ||||
|                 .onSearch=${(value: string) => { | ||||
|                     if (!this.onSearch) return; | ||||
|                     this.onSearch(value); | ||||
|                 }} | ||||
|                 name="search" | ||||
|             ></ak-search-ql>`; | ||||
|         } | ||||
|         return html`<input | ||||
|             class="pf-c-form-control" | ||||
|             name="search" | ||||
|             type="search" | ||||
|             placeholder=${msg("Search...")} | ||||
|             value="${ifDefined(this.value)}" | ||||
|             @search=${(ev: Event) => { | ||||
|                 if (!this.onSearch) return; | ||||
|                 this.onSearch((ev.target as HTMLInputElement).value); | ||||
|             }} | ||||
|         />`; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<form | ||||
|             class="pf-c-input-group" | ||||
|             method="GET" | ||||
|             method="get" | ||||
|             @submit=${(e: Event) => { | ||||
|                 e.preventDefault(); | ||||
|                 if (!this.onSearch) return; | ||||
|                 const el = this.shadowRoot?.querySelector<HTMLInputElement>("input[type=search]"); | ||||
|                 const el = this.shadowRoot?.querySelector<HTMLInputElement | HTMLTextAreaElement>( | ||||
|                     "[name=search]", | ||||
|                 ); | ||||
|                 if (!el) return; | ||||
|                 if (el.value === "") return; | ||||
|                 this.onSearch(el?.value); | ||||
|             }} | ||||
|         > | ||||
|             <input | ||||
|                 class="pf-c-form-control" | ||||
|                 name="search" | ||||
|                 type="search" | ||||
|                 placeholder=${msg("Search...")} | ||||
|                 value="${ifDefined(this.value)}" | ||||
|                 @search=${(ev: Event) => { | ||||
|                     if (!this.onSearch) return; | ||||
|                     this.onSearch((ev.target as HTMLInputElement).value); | ||||
|                 }} | ||||
|             /> | ||||
|             ${this.renderInput()} | ||||
|             <button | ||||
|                 class="pf-c-button pf-m-control" | ||||
|                 type="reset" | ||||
|                 @click=${() => { | ||||
|                     if (!this.onSearch) return; | ||||
|                     this.value = ""; | ||||
|                     this.onSearch(""); | ||||
|                 }} | ||||
|             > | ||||
|  | ||||
| @ -19,7 +19,11 @@ describe("ak-empty-state", () => { | ||||
|     }); | ||||
|  | ||||
|     it("should render the default loader", async () => { | ||||
|         render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`); | ||||
|         render( | ||||
|             html`<ak-empty-state loading | ||||
|                 ><span slot="header">${msg("Loading")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
|         const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon"); | ||||
|         await expect(empty).toExist(); | ||||
| @ -29,7 +33,11 @@ describe("ak-empty-state", () => { | ||||
|     }); | ||||
|  | ||||
|     it("should handle standard boolean", async () => { | ||||
|         render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`); | ||||
|         render( | ||||
|             html`<ak-empty-state loading | ||||
|                 ><span slot="header">${msg("Loading")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
|         const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon"); | ||||
|         await expect(empty).toExist(); | ||||
| @ -39,7 +47,11 @@ describe("ak-empty-state", () => { | ||||
|     }); | ||||
|  | ||||
|     it("should render a static empty state", async () => { | ||||
|         render(html`<ak-empty-state header=${msg("No messages found")}> </ak-empty-state>`); | ||||
|         render( | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No messages found")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
|         const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon"); | ||||
|         await expect(empty).toExist(); | ||||
| @ -51,7 +63,8 @@ describe("ak-empty-state", () => { | ||||
|  | ||||
|     it("should render a slotted message", async () => { | ||||
|         render( | ||||
|             html`<ak-empty-state header=${msg("No messages found")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No messages found")}</span> | ||||
|                 <p slot="body">Try again with a different filter</p> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
							
								
								
									
										2
									
								
								web/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -14,7 +14,7 @@ declare module "module" { | ||||
|          * const relativeDirname = dirname(fileURLToPath(import.meta.url)); | ||||
|          * ``` | ||||
|          */ | ||||
|         // eslint-disable-next-line no-var | ||||
|  | ||||
|         var __dirname: string; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9245,6 +9245,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -7753,6 +7753,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9305,6 +9305,9 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9874,6 +9874,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9857,6 +9857,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9213,6 +9213,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9117,6 +9117,9 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9540,6 +9540,9 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9549,4 +9549,7 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
| </body></file></xliff> | ||||
|  | ||||
| @ -9632,6 +9632,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9604,6 +9604,9 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -6368,6 +6368,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
| </body> | ||||
| </file> | ||||
| </xliff> | ||||
|  | ||||
| @ -9878,12 +9878,18 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="sf9686d31d28fcf7d"> | ||||
|   <source>Show field content</source> | ||||
|   <target>显示字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="sb1b05a7573ab618c"> | ||||
|   <source>Hide field content</source> | ||||
|   <target>隐藏字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
|   <target>使用 Plex 重新验证身份</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -7453,6 +7453,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -3011,11 +3011,6 @@ doesn't pass when either or both of the selected options are equal or above the | ||||
|         <source>Load servers</source> | ||||
|         <target>加载服务器</target> | ||||
|          | ||||
|       </trans-unit> | ||||
|       <trans-unit id="s24f405197ede5ebb"> | ||||
|         <source>Re-authenticate with plex</source> | ||||
|         <target>使用 Plex 重新验证身份</target> | ||||
|          | ||||
|       </trans-unit> | ||||
|       <trans-unit id="sc297b2e13c28ecf9"> | ||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||
| @ -9880,6 +9875,18 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| <trans-unit id="sb3d5c0a0501669df"> | ||||
|   <source>Generate New Certificate-Key Pair</source> | ||||
|   <target>生成新的证书密钥对</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="sf9686d31d28fcf7d"> | ||||
|   <source>Show field content</source> | ||||
|   <target>显示字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="sb1b05a7573ab618c"> | ||||
|   <source>Hide field content</source> | ||||
|   <target>隐藏字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
|   <target>使用 Plex 重新验证身份</target> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9192,6 +9192,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -70,9 +70,6 @@ To check if your config has been applied correctly, you can run the following co | ||||
| - `AUTHENTIK_POSTGRESQL__USER`: Database user | ||||
| - `AUTHENTIK_POSTGRESQL__PORT`: Database port, defaults to 5432 | ||||
| - `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD` | ||||
|   {/* TODO: Temporarily deactivated feature, see https://github.com/goauthentik/authentik/issues/14320 */} | ||||
|   {/* - `AUTHENTIK_POSTGRESQL__USE_POOL`: Use a [connection pool](https://docs.djangoproject.com/en/stable/ref/databases/#connection-pool) for PostgreSQL connections. Defaults to `false`. :ak-version[2025.4] */} | ||||
|   {/* - `AUTHENTIK_POSTGRESQL__POOL_OPTIONS`: Extra configuration to pass to the [ConnectionPool object](https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.ConnectionPool) when it is created. Must be a base64-encoded JSON dictionary. Ignored when `USE_POOL` is set to `false`. :ak-version[2025.4] */} | ||||
| - `AUTHENTIK_POSTGRESQL__USE_PGBOUNCER`: Adjust configuration to support connection to PgBouncer. Deprecated, see below | ||||
| - `AUTHENTIK_POSTGRESQL__USE_PGPOOL`: Adjust configuration to support connection to Pgpool. Deprecated, see below | ||||
| - `AUTHENTIK_POSTGRESQL__SSLMODE`: Strictness of ssl verification. Defaults to `"verify-ca"` | ||||
| @ -85,7 +82,7 @@ To check if your config has been applied correctly, you can run the following co | ||||
|  | ||||
| The PostgreSQL settings `HOST`, `PORT`, `USER`, and `PASSWORD` support hot-reloading. Adding and removing read replicas doesn't support hot-reloading. | ||||
|  | ||||
| - `AUTHENTIK_POSTGRESQL__DEFAULT_SCHEMA`:ak-version[2024.12] | ||||
| - `AUTHENTIK_POSTGRESQL__DEFAULT_SCHEMA` :ak-version[2024.12] | ||||
|  | ||||
|     The name of the schema used by default in the database. Defaults to `public`. | ||||
|  | ||||
|  | ||||
| @ -142,6 +142,17 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2025.6 | ||||
| - tenants: fix tenant aware celery scheduler (cherry-pick #14921) | ||||
| - web/user: fix user settings flow not loading (cherry-pick #14911) (#14930) | ||||
|  | ||||
| ## Fixed in 2025.6.2 | ||||
|  | ||||
| - brands: fix custom_css being escaped (cherry-pick #14994) (#14996) | ||||
| - core: bump django from 5.1.10 to 5.1.11 (cherry-pick #14997) (#15010) | ||||
| - core: bump django from 5.1.9 to 5.1.10 (cherry-pick #14951) (#15008) | ||||
| - internal/outpost: fix incorrect usage of golang SHA API (cherry-pick #14981) (#14982) | ||||
| - providers/rac: fixes prompt data not being merged with connection_settings (cherry-pick #15037) (#15038) | ||||
| - stages/email: Only attach logo to email if used (cherry-pick #14835) (#14969) | ||||
| - web/elements: fix dual select without sortBy (cherry-pick #14977) (#14979) | ||||
| - web/elements: fix typo in localeComparator (cherry-pick #15054) (#15055) | ||||
|  | ||||
| ## API Changes | ||||
|  | ||||
| #### What's New | ||||
|  | ||||
| @ -27,3 +27,29 @@ uv run ak create_recovery_key 10 akadmin | ||||
| ``` | ||||
|  | ||||
| This will output a link, that can be used to instantly gain access to authentik as the user specified above. The link is valid for amount of years specified above, in this case, 10 years. | ||||
|  | ||||
| ## Can't access initial setup flow during installation steps | ||||
|  | ||||
| If you're unable to access the initial setup flow (`/if/flow/initial-setup/`) immediately after installing authentik, first try restarting the containers because this often resolves temporary issues. | ||||
|  | ||||
| However, if the issue persists after restarting, you can directly set the admin password using the following commands: | ||||
|  | ||||
| Docker Compose deployments: | ||||
|  | ||||
|     ```bash | ||||
|     docker compose exec server ak changepassword akadmin | ||||
|     ``` | ||||
|  | ||||
| Kubernetes deployments: | ||||
|  | ||||
|     ```bash | ||||
|     kubectl exec -it deployment/authentik-server -c server -- ak changepassword akadmin | ||||
|     ``` | ||||
|  | ||||
| After following the prompts to set a new password, you can then login via: `https://authentik.company/if/flow/default-authentication-flow/?next=%2F` | ||||
|  | ||||
| After logging in, you can set the email address and other settings for the account by navigating to **Directory** > **Users** and editing the user account. | ||||
|  | ||||
| :::note | ||||
| This method bypasses the initial setup flow and should only be used as a last resort. The initial setup flow is the recommended method to configure the administrator user. | ||||
| ::: | ||||
|  | ||||
| @ -49,7 +49,7 @@ After you are connected, execute these commands to create a database backup: | ||||
| cd /bitnami/postgresql/ | ||||
|  | ||||
| # Set the PostgreSQL password from environment variable | ||||
| export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD | ||||
| export PGPASSWORD=$(cat $POSTGRES_PASSWORD_FILE) | ||||
|  | ||||
| # Create a full database dump | ||||
| pg_dump -U $POSTGRES_USER $POSTGRES_DB > /bitnami/postgresql/dump.sql | ||||
| @ -117,7 +117,7 @@ cd /bitnami/postgresql/ | ||||
| ls -lh dump.sql | ||||
|  | ||||
| # Set the PostgreSQL password | ||||
| export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD | ||||
| export PGPASSWORD=$(cat $POSTGRES_PASSWORD_FILE) | ||||
|  | ||||
| # Import the database dump | ||||
| psql -U $POSTGRES_USER $POSTGRES_DB < dump.sql | ||||
|  | ||||
| @ -161,6 +161,10 @@ Depending on your Nextcloud configuration, you may need to use `https://nextclou | ||||
|  | ||||
|     - **Use unique user ID**: If this option is disabled, Nextcloud will use the mapped user ID as the Federated Cloud ID. | ||||
|  | ||||
|     :::note | ||||
|     If authentik and Nextcloud are running on the same host, you will need to add `'allow_local_remote_servers' => true` to your nextcloud `config.php` file. This setting allows remote servers with local addresses. | ||||
|     ::: | ||||
|  | ||||
|     :::tip | ||||
|     To avoid a hashed Federated Cloud ID, deselect **Use unique user ID** and use `user_id` for the User ID mapping. | ||||
|     ::: | ||||
|  | ||||
| @ -40,6 +40,7 @@ To support the integration of Zipline with authentik, you need to create an appl | ||||
|     - Note the **Client ID** and **Client Secret** values because they will be required later. | ||||
|     - Set a `Strict` redirect URI to `https://zipline.company/api/auth/oauth/oidc`. | ||||
|     - Select any available signing key. | ||||
|     - Under **Advanced Protocol Settings** > **Scopes**, add `authentik default OAuth Mapping: OpenID 'offline_access'` to the **Selected Scopes** list. | ||||
| - **Configure Bindings** _(optional)_: Create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page. | ||||
|  | ||||
| 3. Click **Submit** to save the new application and provider. | ||||
|  | ||||
							
								
								
									
										156
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										156
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -22,7 +22,7 @@ | ||||
|                 "clsx": "^2.1.1", | ||||
|                 "docusaurus-plugin-openapi-docs": "^4.4.0", | ||||
|                 "docusaurus-theme-openapi-docs": "^4.4.0", | ||||
|                 "postcss": "^8.5.5", | ||||
|                 "postcss": "^8.5.6", | ||||
|                 "prism-react-renderer": "^2.4.1", | ||||
|                 "react": "^18.3.1", | ||||
|                 "react-before-after-slider-component": "^1.1.8", | ||||
| @ -40,13 +40,13 @@ | ||||
|                 "@goauthentik/prettier-config": "^1.0.5", | ||||
|                 "@goauthentik/tsconfig": "^1.0.4", | ||||
|                 "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|                 "@types/lodash": "^4.17.17", | ||||
|                 "@types/node": "^24.0.1", | ||||
|                 "@types/lodash": "^4.17.18", | ||||
|                 "@types/node": "^24.0.3", | ||||
|                 "@types/postman-collection": "^3.5.11", | ||||
|                 "@types/react": "^18.3.22", | ||||
|                 "@types/semver": "^7.7.0", | ||||
|                 "@typescript-eslint/eslint-plugin": "^8.34.0", | ||||
|                 "@typescript-eslint/parser": "^8.34.0", | ||||
|                 "@typescript-eslint/eslint-plugin": "^8.34.1", | ||||
|                 "@typescript-eslint/parser": "^8.34.1", | ||||
|                 "cross-env": "^7.0.3", | ||||
|                 "eslint": "^9.29.0", | ||||
|                 "fast-glob": "^3.3.3", | ||||
| @ -54,7 +54,7 @@ | ||||
|                 "prettier": "^3.5.3", | ||||
|                 "prettier-plugin-packagejson": "^2.5.15", | ||||
|                 "typescript": "^5.8.3", | ||||
|                 "typescript-eslint": "^8.34.0" | ||||
|                 "typescript-eslint": "^8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=22.14.0" | ||||
| @ -6580,9 +6580,9 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@types/lodash": { | ||||
|             "version": "4.17.17", | ||||
|             "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", | ||||
|             "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", | ||||
|             "version": "4.17.18", | ||||
|             "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.18.tgz", | ||||
|             "integrity": "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==", | ||||
|             "dev": true, | ||||
|             "license": "MIT" | ||||
|         }, | ||||
| @ -6614,9 +6614,9 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@types/node": { | ||||
|             "version": "24.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", | ||||
|             "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", | ||||
|             "version": "24.0.3", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", | ||||
|             "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "undici-types": "~7.8.0" | ||||
| @ -6830,17 +6830,17 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", | ||||
|             "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", | ||||
|             "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/regexpp": "^4.10.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/type-utils": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/type-utils": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "graphemer": "^1.4.0", | ||||
|                 "ignore": "^7.0.0", | ||||
|                 "natural-compare": "^1.4.0", | ||||
| @ -6854,7 +6854,7 @@ | ||||
|                 "url": "https://opencollective.com/typescript-eslint" | ||||
|             }, | ||||
|             "peerDependencies": { | ||||
|                 "@typescript-eslint/parser": "^8.34.0", | ||||
|                 "@typescript-eslint/parser": "^8.34.1", | ||||
|                 "eslint": "^8.57.0 || ^9.0.0", | ||||
|                 "typescript": ">=4.8.4 <5.9.0" | ||||
|             } | ||||
| @ -6870,16 +6870,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/parser": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", | ||||
|             "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", | ||||
|             "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -6895,14 +6895,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/project-service": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", | ||||
|             "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", | ||||
|             "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.0", | ||||
|                 "@typescript-eslint/types": "^8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.1", | ||||
|                 "@typescript-eslint/types": "^8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -6917,14 +6917,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/scope-manager": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", | ||||
|             "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", | ||||
|             "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -6935,9 +6935,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/tsconfig-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -6952,14 +6952,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/type-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "ts-api-utils": "^2.1.0" | ||||
|             }, | ||||
| @ -6976,9 +6976,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/types": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", | ||||
|             "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -6990,16 +6990,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", | ||||
|             "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/project-service": "8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/project-service": "8.34.1", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "fast-glob": "^3.3.2", | ||||
|                 "is-glob": "^4.0.3", | ||||
| @ -7019,9 +7019,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { | ||||
|             "version": "2.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", | ||||
|             "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", | ||||
|             "version": "2.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | ||||
|             "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
| @ -7045,16 +7045,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.7.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0" | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -7069,14 +7069,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/visitor-keys": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", | ||||
|             "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", | ||||
|             "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "eslint-visitor-keys": "^4.2.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "eslint-visitor-keys": "^4.2.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -20672,9 +20672,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/postcss": { | ||||
|             "version": "8.5.5", | ||||
|             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", | ||||
|             "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", | ||||
|             "version": "8.5.6", | ||||
|             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", | ||||
|             "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", | ||||
|             "funding": [ | ||||
|                 { | ||||
|                     "type": "opencollective", | ||||
| @ -26387,15 +26387,15 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/typescript-eslint": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", | ||||
|             "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", | ||||
|             "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.0", | ||||
|                 "@typescript-eslint/parser": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0" | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.1", | ||||
|                 "@typescript-eslint/parser": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
|  | ||||
| @ -37,7 +37,7 @@ | ||||
|         "clsx": "^2.1.1", | ||||
|         "docusaurus-plugin-openapi-docs": "^4.4.0", | ||||
|         "docusaurus-theme-openapi-docs": "^4.4.0", | ||||
|         "postcss": "^8.5.5", | ||||
|         "postcss": "^8.5.6", | ||||
|         "prism-react-renderer": "^2.4.1", | ||||
|         "react": "^18.3.1", | ||||
|         "react-before-after-slider-component": "^1.1.8", | ||||
| @ -55,13 +55,13 @@ | ||||
|         "@goauthentik/prettier-config": "^1.0.5", | ||||
|         "@goauthentik/tsconfig": "^1.0.4", | ||||
|         "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|         "@types/lodash": "^4.17.17", | ||||
|         "@types/node": "^24.0.1", | ||||
|         "@types/lodash": "^4.17.18", | ||||
|         "@types/node": "^24.0.3", | ||||
|         "@types/postman-collection": "^3.5.11", | ||||
|         "@types/react": "^18.3.22", | ||||
|         "@types/semver": "^7.7.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.34.0", | ||||
|         "@typescript-eslint/parser": "^8.34.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.34.1", | ||||
|         "@typescript-eslint/parser": "^8.34.1", | ||||
|         "cross-env": "^7.0.3", | ||||
|         "eslint": "^9.29.0", | ||||
|         "fast-glob": "^3.3.3", | ||||
| @ -69,7 +69,7 @@ | ||||
|         "prettier": "^3.5.3", | ||||
|         "prettier-plugin-packagejson": "^2.5.15", | ||||
|         "typescript": "^5.8.3", | ||||
|         "typescript-eslint": "^8.34.0" | ||||
|         "typescript-eslint": "^8.34.1" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "@rspack/binding-darwin-arm64": "1.3.15", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Marc 'risson' Schmitt
					Marc 'risson' Schmitt