diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/propertymappings.py index 1e7436be9..d0fa7267b 100644 --- a/authentik/core/api/propertymappings.py +++ b/authentik/core/api/propertymappings.py @@ -19,6 +19,7 @@ from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer, TypeCreateSerializer from authentik.core.expression.evaluator import PropertyMappingEvaluator from authentik.core.models import PropertyMapping +from authentik.enterprise.apps import EnterpriseConfig from authentik.events.utils import sanitize_item from authentik.lib.utils.reflection import all_subclasses from authentik.policies.api.exec import PolicyTestSerializer @@ -95,6 +96,7 @@ class PropertyMappingViewSet( "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, + "requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig), } ) return Response(TypeCreateSerializer(data, many=True).data) diff --git a/authentik/core/api/providers.py b/authentik/core/api/providers.py index a5095dcde..6c0f4db06 100644 --- a/authentik/core/api/providers.py +++ b/authentik/core/api/providers.py @@ -16,6 +16,7 @@ from rest_framework.viewsets import GenericViewSet from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.models import Provider +from authentik.enterprise.apps import EnterpriseConfig from authentik.lib.utils.reflection import all_subclasses @@ -113,6 +114,7 @@ class ProviderViewSet( "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, + "requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig), } ) data.append( diff --git a/authentik/core/api/utils.py b/authentik/core/api/utils.py index c7a188f5c..c79fec22e 100644 --- a/authentik/core/api/utils.py +++ b/authentik/core/api/utils.py @@ -5,7 +5,7 @@ from django.db.models import Model from drf_spectacular.extensions import OpenApiSerializerFieldExtension from drf_spectacular.plumbing import build_basic_type from drf_spectacular.types import OpenApiTypes -from rest_framework.fields import CharField, IntegerField, JSONField +from rest_framework.fields import BooleanField, CharField, IntegerField, JSONField from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError @@ -74,6 +74,7 @@ class TypeCreateSerializer(PassiveSerializer): description = CharField(required=True) component = CharField(required=True) model_name = CharField(required=True) + requires_enterprise = BooleanField(default=False) class CacheSerializer(PassiveSerializer): diff --git a/authentik/enterprise/api.py b/authentik/enterprise/api.py index fdf0a11fc..c13e34b6d 100644 --- a/authentik/enterprise/api.py +++ b/authentik/enterprise/api.py @@ -2,9 +2,11 @@ from datetime import datetime, timedelta from django.utils.timezone import now +from django.utils.translation import gettext as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework.decorators import action +from rest_framework.exceptions import ValidationError from rest_framework.fields import BooleanField, CharField, DateTimeField, IntegerField from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request @@ -20,6 +22,18 @@ from authentik.enterprise.models import License, LicenseKey from authentik.root.install_id import get_install_id +class EnterpriseRequiredMixin: + """Mixin to validate that a valid enterprise license + exists before allowing to safe the object""" + + def validate(self, attrs: dict) -> dict: + """Check that a valid license exists""" + total = LicenseKey.get_total() + if not total.is_valid(): + raise ValidationError(_("Enterprise is required to create/update this object.")) + return super().validate(attrs) + + class LicenseSerializer(ModelSerializer): """License Serializer""" diff --git a/authentik/enterprise/apps.py b/authentik/enterprise/apps.py index 2d918da17..a0b9bed6d 100644 --- a/authentik/enterprise/apps.py +++ b/authentik/enterprise/apps.py @@ -2,7 +2,11 @@ from authentik.blueprints.apps import ManagedAppConfig -class AuthentikEnterpriseConfig(ManagedAppConfig): +class EnterpriseConfig(ManagedAppConfig): + """Base app config for all enterprise apps""" + + +class AuthentikEnterpriseConfig(EnterpriseConfig): """Enterprise app config""" name = "authentik.enterprise" diff --git a/authentik/enterprise/providers/rac/api/endpoints.py b/authentik/enterprise/providers/rac/api/endpoints.py index b0b0239c5..1af281ef8 100644 --- a/authentik/enterprise/providers/rac/api/endpoints.py +++ b/authentik/enterprise/providers/rac/api/endpoints.py @@ -15,6 +15,7 @@ from structlog.stdlib import get_logger from authentik.core.api.used_by import UsedByMixin from authentik.core.models import Provider +from authentik.enterprise.api import EnterpriseRequiredMixin from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer from authentik.enterprise.providers.rac.models import Endpoint from authentik.policies.engine import PolicyEngine @@ -28,7 +29,7 @@ def user_endpoint_cache_key(user_pk: str) -> str: return f"goauthentik.io/providers/rac/endpoint_access/{user_pk}" -class EndpointSerializer(ModelSerializer): +class EndpointSerializer(EnterpriseRequiredMixin, ModelSerializer): """Endpoint Serializer""" provider_obj = RACProviderSerializer(source="provider", read_only=True) @@ -59,6 +60,7 @@ class EndpointSerializer(ModelSerializer): "property_mappings", "auth_mode", "launch_url", + "maximum_connections", ] diff --git a/authentik/enterprise/providers/rac/api/property_mappings.py b/authentik/enterprise/providers/rac/api/property_mappings.py index 35daec95c..4afef68bb 100644 --- a/authentik/enterprise/providers/rac/api/property_mappings.py +++ b/authentik/enterprise/providers/rac/api/property_mappings.py @@ -5,10 +5,11 @@ from rest_framework.viewsets import ModelViewSet from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import JSONDictField +from authentik.enterprise.api import EnterpriseRequiredMixin from authentik.enterprise.providers.rac.models import RACPropertyMapping -class RACPropertyMappingSerializer(PropertyMappingSerializer): +class RACPropertyMappingSerializer(EnterpriseRequiredMixin, PropertyMappingSerializer): """RACPropertyMapping Serializer""" static_settings = JSONDictField() diff --git a/authentik/enterprise/providers/rac/api/providers.py b/authentik/enterprise/providers/rac/api/providers.py index 6dd4f9f82..cda6c2af3 100644 --- a/authentik/enterprise/providers/rac/api/providers.py +++ b/authentik/enterprise/providers/rac/api/providers.py @@ -4,10 +4,11 @@ from rest_framework.viewsets import ModelViewSet from authentik.core.api.providers import ProviderSerializer from authentik.core.api.used_by import UsedByMixin +from authentik.enterprise.api import EnterpriseRequiredMixin from authentik.enterprise.providers.rac.models import RACProvider -class RACProviderSerializer(ProviderSerializer): +class RACProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer): """RACProvider Serializer""" outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all") diff --git a/authentik/enterprise/providers/rac/apps.py b/authentik/enterprise/providers/rac/apps.py index 973159bb9..13930faae 100644 --- a/authentik/enterprise/providers/rac/apps.py +++ b/authentik/enterprise/providers/rac/apps.py @@ -1,8 +1,8 @@ """RAC app config""" -from authentik.blueprints.apps import ManagedAppConfig +from authentik.enterprise.apps import EnterpriseConfig -class AuthentikEnterpriseProviderRAC(ManagedAppConfig): +class AuthentikEnterpriseProviderRAC(EnterpriseConfig): """authentik enterprise rac app config""" name = "authentik.enterprise.providers.rac" diff --git a/authentik/enterprise/providers/rac/migrations/0002_endpoint_maximum_connections.py b/authentik/enterprise/providers/rac/migrations/0002_endpoint_maximum_connections.py new file mode 100644 index 000000000..0760f2313 --- /dev/null +++ b/authentik/enterprise/providers/rac/migrations/0002_endpoint_maximum_connections.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0 on 2024-01-03 23:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_providers_rac", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="endpoint", + name="maximum_connections", + field=models.IntegerField(default=1), + ), + ] diff --git a/authentik/enterprise/providers/rac/models.py b/authentik/enterprise/providers/rac/models.py index d79bbd54c..927bd23fe 100644 --- a/authentik/enterprise/providers/rac/models.py +++ b/authentik/enterprise/providers/rac/models.py @@ -35,7 +35,7 @@ class AuthenticationMode(models.TextChoices): class RACProvider(Provider): - """Remotely access computers/servers""" + """Remotely access computers/servers via RDP/SSH/VNC.""" settings = models.JSONField(default=dict) auth_mode = models.TextField( @@ -81,6 +81,7 @@ class Endpoint(SerializerModel, PolicyBindingModel): settings = models.JSONField(default=dict) auth_mode = models.TextField(choices=AuthenticationMode.choices) provider = models.ForeignKey("RACProvider", on_delete=models.CASCADE) + maximum_connections = models.IntegerField(default=1) property_mappings = models.ManyToManyField( "authentik_core.PropertyMapping", default=None, blank=True diff --git a/authentik/enterprise/providers/rac/tests/test_endpoints_api.py b/authentik/enterprise/providers/rac/tests/test_endpoints_api.py index 0a659bccd..3000b345c 100644 --- a/authentik/enterprise/providers/rac/tests/test_endpoints_api.py +++ b/authentik/enterprise/providers/rac/tests/test_endpoints_api.py @@ -81,6 +81,7 @@ class TestEndpointsAPI(APITestCase): }, "protocol": "rdp", "host": self.allowed.host, + "maximum_connections": 1, "settings": {}, "property_mappings": [], "auth_mode": "", @@ -131,6 +132,7 @@ class TestEndpointsAPI(APITestCase): }, "protocol": "rdp", "host": self.allowed.host, + "maximum_connections": 1, "settings": {}, "property_mappings": [], "auth_mode": "", @@ -158,6 +160,7 @@ class TestEndpointsAPI(APITestCase): }, "protocol": "rdp", "host": self.denied.host, + "maximum_connections": 1, "settings": {}, "property_mappings": [], "auth_mode": "", diff --git a/authentik/enterprise/providers/rac/views.py b/authentik/enterprise/providers/rac/views.py index 31a25c721..4b93aee76 100644 --- a/authentik/enterprise/providers/rac/views.py +++ b/authentik/enterprise/providers/rac/views.py @@ -5,11 +5,13 @@ from django.http import Http404, HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.timezone import now +from django.utils.translation import gettext as _ from authentik.core.models import Application, AuthenticatedSession from authentik.core.views.interface import InterfaceView from authentik.enterprise.policy import EnterprisePolicyAccessView from authentik.enterprise.providers.rac.models import ConnectionToken, Endpoint, RACProvider +from authentik.events.models import Event, EventAction from authentik.flows.challenge import RedirectChallenge from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.models import in_memory_stage @@ -43,6 +45,7 @@ class RACStartView(EnterprisePolicyAccessView): plan.insert_stage( in_memory_stage( RACFinalStage, + application=self.application, endpoint=self.endpoint, provider=self.provider, ) @@ -77,29 +80,51 @@ class RACInterface(InterfaceView): class RACFinalStage(RedirectStage): """RAC Connection final stage, set the connection token in the stage""" + endpoint: Endpoint + provider: RACProvider + application: Application + def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - endpoint: Endpoint = self.executor.current_stage.endpoint - engine = PolicyEngine(endpoint, self.request.user, self.request) + self.endpoint = self.executor.current_stage.endpoint + self.provider = self.executor.current_stage.provider + self.application = self.executor.current_stage.application + # Check policies bound to endpoint directly + engine = PolicyEngine(self.endpoint, self.request.user, self.request) engine.use_cache = False engine.build() passing = engine.result if not passing.passing: return self.executor.stage_invalid(", ".join(passing.messages)) + # Check if we're already at the maximum connection limit + all_tokens = ConnectionToken.filter_not_expired( + endpoint=self.endpoint, + ).exclude(endpoint__maximum_connections__lte=-1) + if all_tokens.count() >= self.endpoint.maximum_connections: + msg = [_("Maximum connection limit reached.")] + # Check if any other tokens exist for the current user, and inform them + # they are already connected + if all_tokens.filter(session__user=self.request.user).exists(): + msg.append(_("(You are already connected in another tab/window)")) + return self.executor.stage_invalid(" ".join(msg)) return super().dispatch(request, *args, **kwargs) def get_challenge(self, *args, **kwargs) -> RedirectChallenge: - endpoint: Endpoint = self.executor.current_stage.endpoint - provider: RACProvider = self.executor.current_stage.provider token = ConnectionToken.objects.create( - provider=provider, - endpoint=endpoint, + provider=self.provider, + endpoint=self.endpoint, settings=self.executor.plan.context.get("connection_settings", {}), session=AuthenticatedSession.objects.filter( session_key=self.request.session.session_key ).first(), - expires=now() + timedelta_from_string(provider.connection_expiry), + expires=now() + timedelta_from_string(self.provider.connection_expiry), expiring=True, ) + Event.new( + EventAction.AUTHORIZE_APPLICATION, + authorized_application=self.application, + flow=self.executor.plan.flow_pk, + endpoint=self.endpoint.name, + ).from_http(self.request) setattr( self.executor.current_stage, "destination", diff --git a/authentik/events/middleware.py b/authentik/events/middleware.py index 7834bae5e..ea7e6001f 100644 --- a/authentik/events/middleware.py +++ b/authentik/events/middleware.py @@ -20,6 +20,7 @@ from authentik.core.models import ( User, UserSourceConnection, ) +from authentik.enterprise.providers.rac.models import ConnectionToken from authentik.events.models import Event, EventAction, Notification from authentik.events.utils import model_to_dict from authentik.flows.models import FlowToken, Stage @@ -54,6 +55,7 @@ IGNORED_MODELS = ( SCIMUser, SCIMGroup, Reputation, + ConnectionToken, ) diff --git a/authentik/providers/oauth2/constants.py b/authentik/providers/oauth2/constants.py index 81460b707..baaebbd60 100644 --- a/authentik/providers/oauth2/constants.py +++ b/authentik/providers/oauth2/constants.py @@ -7,8 +7,8 @@ GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials" GRANT_TYPE_PASSWORD = "password" # nosec GRANT_TYPE_DEVICE_CODE = "urn:ietf:params:oauth:grant-type:device_code" -CLIENT_ASSERTION_TYPE = "client_assertion_type" CLIENT_ASSERTION = "client_assertion" +CLIENT_ASSERTION_TYPE = "client_assertion_type" CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" PROMPT_NONE = "none" @@ -18,9 +18,9 @@ PROMPT_LOGIN = "login" SCOPE_OPENID = "openid" SCOPE_OPENID_PROFILE = "profile" SCOPE_OPENID_EMAIL = "email" +SCOPE_OFFLINE_ACCESS = "offline_access" -# https://www.iana.org/assignments/oauth-parameters/\ -# oauth-parameters.xhtml#pkce-code-challenge-method +# https://www.iana.org/assignments/oauth-parameters/auth-parameters.xhtml#pkce-code-challenge-method PKCE_METHOD_PLAIN = "plain" PKCE_METHOD_S256 = "S256" @@ -36,6 +36,12 @@ SCOPE_GITHUB_USER_READ = "read:user" SCOPE_GITHUB_USER_EMAIL = "user:email" # Read info about teams SCOPE_GITHUB_ORG_READ = "read:org" +SCOPE_GITHUB = { + SCOPE_GITHUB_USER, + SCOPE_GITHUB_USER_READ, + SCOPE_GITHUB_USER_EMAIL, + SCOPE_GITHUB_ORG_READ, +} ACR_AUTHENTIK_DEFAULT = "goauthentik.io/providers/oauth2/default" diff --git a/authentik/providers/oauth2/errors.py b/authentik/providers/oauth2/errors.py index 1788c9370..fd124cb91 100644 --- a/authentik/providers/oauth2/errors.py +++ b/authentik/providers/oauth2/errors.py @@ -127,7 +127,7 @@ class AuthorizeError(OAuth2Error): "account_selection_required": ( "The End-User is required to select a session at the Authorization Server" ), - "consent_required": "The Authorization Server requires End-Userconsent", + "consent_required": "The Authorization Server requires End-User consent", "invalid_request_uri": ( "The request_uri in the Authorization Request returns an error or contains invalid data" ), diff --git a/authentik/providers/oauth2/tests/test_authorize.py b/authentik/providers/oauth2/tests/test_authorize.py index 91cdc330a..7903c685c 100644 --- a/authentik/providers/oauth2/tests/test_authorize.py +++ b/authentik/providers/oauth2/tests/test_authorize.py @@ -5,6 +5,7 @@ from django.test import RequestFactory from django.urls import reverse from django.utils.timezone import now +from authentik.blueprints.tests import apply_blueprint from authentik.core.models import Application from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.events.models import Event, EventAction @@ -18,6 +19,7 @@ from authentik.providers.oauth2.models import ( AuthorizationCode, GrantTypes, OAuth2Provider, + ScopeMapping, ) from authentik.providers.oauth2.tests.utils import OAuthTestCase from authentik.providers.oauth2.views.authorize import OAuthAuthorizationParams @@ -172,14 +174,24 @@ class TestAuthorize(OAuthTestCase): ) OAuthAuthorizationParams.from_request(request) + @apply_blueprint("system/providers-oauth2.yaml") def test_response_type(self): """test response_type""" - OAuth2Provider.objects.create( + provider = OAuth2Provider.objects.create( name=generate_id(), client_id="test", authorization_flow=create_test_flow(), redirect_uris="http://local.invalid/Foo", ) + provider.property_mappings.set( + ScopeMapping.objects.filter( + managed__in=[ + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-profile", + ] + ) + ) request = self.factory.get( "/", data={ @@ -292,6 +304,7 @@ class TestAuthorize(OAuthTestCase): delta=5, ) + @apply_blueprint("system/providers-oauth2.yaml") def test_full_implicit(self): """Test full authorization""" flow = create_test_flow() @@ -302,6 +315,15 @@ class TestAuthorize(OAuthTestCase): redirect_uris="http://localhost", signing_key=self.keypair, ) + provider.property_mappings.set( + ScopeMapping.objects.filter( + managed__in=[ + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-profile", + ] + ) + ) Application.objects.create(name="app", slug="app", provider=provider) state = generate_id() user = create_test_admin_user() @@ -409,6 +431,7 @@ class TestAuthorize(OAuthTestCase): delta=5, ) + @apply_blueprint("system/providers-oauth2.yaml") def test_full_form_post_id_token(self): """Test full authorization (form_post response)""" flow = create_test_flow() @@ -419,6 +442,15 @@ class TestAuthorize(OAuthTestCase): redirect_uris="http://localhost", signing_key=self.keypair, ) + provider.property_mappings.set( + ScopeMapping.objects.filter( + managed__in=[ + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-profile", + ] + ) + ) app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider) state = generate_id() user = create_test_admin_user() @@ -440,6 +472,7 @@ class TestAuthorize(OAuthTestCase): reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), ) token: AccessToken = AccessToken.objects.filter(user=user).first() + self.assertIsNotNone(token) self.assertJSONEqual( response.content.decode(), { diff --git a/authentik/providers/oauth2/tests/test_token.py b/authentik/providers/oauth2/tests/test_token.py index 79b3b13fe..5904d38be 100644 --- a/authentik/providers/oauth2/tests/test_token.py +++ b/authentik/providers/oauth2/tests/test_token.py @@ -6,6 +6,7 @@ from django.test import RequestFactory from django.urls import reverse from django.utils import timezone +from authentik.blueprints.tests import apply_blueprint from authentik.core.models import Application from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.events.models import Event, EventAction @@ -21,6 +22,7 @@ from authentik.providers.oauth2.models import ( AuthorizationCode, OAuth2Provider, RefreshToken, + ScopeMapping, ) from authentik.providers.oauth2.tests.utils import OAuthTestCase from authentik.providers.oauth2.views.token import TokenParams @@ -136,21 +138,20 @@ class TestToken(OAuthTestCase): HTTP_AUTHORIZATION=f"Basic {header}", ) access: AccessToken = AccessToken.objects.filter(user=user, provider=provider).first() - refresh: RefreshToken = RefreshToken.objects.filter(user=user, provider=provider).first() self.assertJSONEqual( response.content.decode(), { "access_token": access.token, - "refresh_token": refresh.token, "token_type": TOKEN_TYPE, "expires_in": 3600, "id_token": provider.encode( - refresh.id_token.to_dict(), + access.id_token.to_dict(), ), }, ) self.validate_jwt(access, provider) + @apply_blueprint("system/providers-oauth2.yaml") def test_refresh_token_view(self): """test request param""" provider = OAuth2Provider.objects.create( @@ -159,6 +160,16 @@ class TestToken(OAuthTestCase): redirect_uris="http://local.invalid", signing_key=self.keypair, ) + provider.property_mappings.set( + ScopeMapping.objects.filter( + managed__in=[ + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-profile", + "goauthentik.io/providers/oauth2/scope-offline_access", + ] + ) + ) # Needs to be assigned to an application for iss to be set self.app.provider = provider self.app.save() @@ -170,6 +181,7 @@ class TestToken(OAuthTestCase): token=generate_id(), _id_token=dumps({}), auth_time=timezone.now(), + _scope="offline_access", ) response = self.client.post( reverse("authentik_providers_oauth2:token"), @@ -201,6 +213,7 @@ class TestToken(OAuthTestCase): ) self.validate_jwt(access, provider) + @apply_blueprint("system/providers-oauth2.yaml") def test_refresh_token_view_invalid_origin(self): """test request param""" provider = OAuth2Provider.objects.create( @@ -209,6 +222,16 @@ class TestToken(OAuthTestCase): redirect_uris="http://local.invalid", signing_key=self.keypair, ) + provider.property_mappings.set( + ScopeMapping.objects.filter( + managed__in=[ + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-profile", + "goauthentik.io/providers/oauth2/scope-offline_access", + ] + ) + ) header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode() user = create_test_admin_user() token: RefreshToken = RefreshToken.objects.create( @@ -217,6 +240,7 @@ class TestToken(OAuthTestCase): token=generate_id(), _id_token=dumps({}), auth_time=timezone.now(), + _scope="offline_access", ) response = self.client.post( reverse("authentik_providers_oauth2:token"), @@ -247,6 +271,7 @@ class TestToken(OAuthTestCase): }, ) + @apply_blueprint("system/providers-oauth2.yaml") def test_refresh_token_revoke(self): """test request param""" provider = OAuth2Provider.objects.create( @@ -255,6 +280,16 @@ class TestToken(OAuthTestCase): redirect_uris="http://testserver", signing_key=self.keypair, ) + provider.property_mappings.set( + ScopeMapping.objects.filter( + managed__in=[ + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-profile", + "goauthentik.io/providers/oauth2/scope-offline_access", + ] + ) + ) # Needs to be assigned to an application for iss to be set self.app.provider = provider self.app.save() @@ -266,6 +301,7 @@ class TestToken(OAuthTestCase): token=generate_id(), _id_token=dumps({}), auth_time=timezone.now(), + _scope="offline_access", ) # Create initial refresh token response = self.client.post( diff --git a/authentik/providers/oauth2/urls_root.py b/authentik/providers/oauth2/urls_root.py index b00a90de4..9f7fe05ac 100644 --- a/authentik/providers/oauth2/urls_root.py +++ b/authentik/providers/oauth2/urls_root.py @@ -10,7 +10,7 @@ from authentik.providers.oauth2.views.token import TokenView github_urlpatterns = [ path( "login/oauth/authorize", - AuthorizationFlowInitView.as_view(), + AuthorizationFlowInitView.as_view(github_compat=True), name="github-authorize", ), path( diff --git a/authentik/providers/oauth2/views/authorize.py b/authentik/providers/oauth2/views/authorize.py index 520f02d65..7ad76a642 100644 --- a/authentik/providers/oauth2/views/authorize.py +++ b/authentik/providers/oauth2/views/authorize.py @@ -1,5 +1,5 @@ """authentik OAuth2 Authorization views""" -from dataclasses import dataclass, field +from dataclasses import InitVar, dataclass, field from datetime import timedelta from hashlib import sha256 from json import dumps @@ -41,6 +41,8 @@ from authentik.providers.oauth2.constants import ( PROMPT_CONSENT, PROMPT_LOGIN, PROMPT_NONE, + SCOPE_GITHUB, + SCOPE_OFFLINE_ACCESS, SCOPE_OPENID, TOKEN_TYPE, ) @@ -66,7 +68,6 @@ from authentik.stages.consent.models import ConsentMode, ConsentStage from authentik.stages.consent.stage import ( PLAN_CONTEXT_CONSENT_HEADER, PLAN_CONTEXT_CONSENT_PERMISSIONS, - ConsentStageView, ) LOGGER = get_logger() @@ -86,7 +87,7 @@ class OAuthAuthorizationParams: redirect_uri: str response_type: str response_mode: Optional[str] - scope: list[str] + scope: set[str] state: str nonce: Optional[str] prompt: set[str] @@ -101,8 +102,10 @@ class OAuthAuthorizationParams: code_challenge: Optional[str] = None code_challenge_method: Optional[str] = None + github_compat: InitVar[bool] = False + @staticmethod - def from_request(request: HttpRequest) -> "OAuthAuthorizationParams": + def from_request(request: HttpRequest, github_compat=False) -> "OAuthAuthorizationParams": """ Get all the params used by the Authorization Code Flow (and also for the Implicit and Hybrid). @@ -154,7 +157,7 @@ class OAuthAuthorizationParams: response_type=response_type, response_mode=response_mode, grant_type=grant_type, - scope=query_dict.get("scope", "").split(), + scope=set(query_dict.get("scope", "").split()), state=state, nonce=query_dict.get("nonce"), prompt=ALLOWED_PROMPT_PARAMS.intersection(set(query_dict.get("prompt", "").split())), @@ -162,9 +165,10 @@ class OAuthAuthorizationParams: max_age=int(max_age) if max_age else None, code_challenge=query_dict.get("code_challenge"), code_challenge_method=query_dict.get("code_challenge_method", "plain"), + github_compat=github_compat, ) - def __post_init__(self): + def __post_init__(self, github_compat=False): self.provider: OAuth2Provider = OAuth2Provider.objects.filter( client_id=self.client_id ).first() @@ -172,7 +176,7 @@ class OAuthAuthorizationParams: LOGGER.warning("Invalid client identifier", client_id=self.client_id) raise ClientIdError(client_id=self.client_id) self.check_redirect_uri() - self.check_scope() + self.check_scope(github_compat) self.check_nonce() self.check_code_challenge() @@ -199,8 +203,8 @@ class OAuthAuthorizationParams: if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls): LOGGER.warning( "Invalid redirect uri (regex comparison)", - redirect_uri=self.redirect_uri, - expected=allowed_redirect_urls, + redirect_uri_given=self.redirect_uri, + redirect_uri_expected=allowed_redirect_urls, ) raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) except RegexError as exc: @@ -208,8 +212,8 @@ class OAuthAuthorizationParams: if not any(x == self.redirect_uri for x in allowed_redirect_urls): LOGGER.warning( "Invalid redirect uri (strict comparison)", - redirect_uri=self.redirect_uri, - expected=allowed_redirect_urls, + redirect_uri_given=self.redirect_uri, + redirect_uri_expected=allowed_redirect_urls, ) raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) if self.request: @@ -217,24 +221,50 @@ class OAuthAuthorizationParams: self.redirect_uri, "request_not_supported", self.grant_type, self.state ) - def check_scope(self): + def check_scope(self, github_compat=False): """Ensure openid scope is set in Hybrid flows, or when requesting an id_token""" - if len(self.scope) == 0: - default_scope_names = set( - ScopeMapping.objects.filter(provider__in=[self.provider]).values_list( - "scope_name", flat=True - ) + default_scope_names = set( + ScopeMapping.objects.filter(provider__in=[self.provider]).values_list( + "scope_name", flat=True ) + ) + if len(self.scope) == 0: self.scope = default_scope_names LOGGER.info( "No scopes requested, defaulting to all configured scopes", scopes=self.scope ) + scopes_to_check = self.scope + if github_compat: + scopes_to_check = self.scope - SCOPE_GITHUB + if not scopes_to_check.issubset(default_scope_names): + LOGGER.info( + "Application requested scopes not configured, setting to overlap", + scope_allowed=default_scope_names, + scope_given=self.scope, + ) + self.scope = self.scope.intersection(default_scope_names) if SCOPE_OPENID not in self.scope and ( self.grant_type == GrantTypes.HYBRID or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN] ): LOGGER.warning("Missing 'openid' scope.") raise AuthorizeError(self.redirect_uri, "invalid_scope", self.grant_type, self.state) + if SCOPE_OFFLINE_ACCESS in self.scope: + # https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess + if PROMPT_CONSENT not in self.prompt: + raise AuthorizeError( + self.redirect_uri, "consent_required", self.grant_type, self.state + ) + if self.response_type not in [ + ResponseTypes.CODE, + ResponseTypes.CODE_TOKEN, + ResponseTypes.CODE_ID_TOKEN, + ResponseTypes.CODE_ID_TOKEN_TOKEN, + ]: + # offline_access requires a response type that has some sort of token + # Spec says to ignore the scope when the response_type wouldn't result + # in an authorization code being generated + self.scope.remove(SCOPE_OFFLINE_ACCESS) def check_nonce(self): """Nonce parameter validation.""" @@ -297,6 +327,9 @@ class AuthorizationFlowInitView(PolicyAccessView): """OAuth2 Flow initializer, checks access to application and starts flow""" params: OAuthAuthorizationParams + # Enable GitHub compatibility (only allow for scopes which are handled + # differently for github compat) + github_compat = False def pre_permission_check(self): """Check prompt parameter before checking permission/authentication, @@ -305,7 +338,9 @@ class AuthorizationFlowInitView(PolicyAccessView): if len(self.request.GET) < 1: raise Http404 try: - self.params = OAuthAuthorizationParams.from_request(self.request) + self.params = OAuthAuthorizationParams.from_request( + self.request, github_compat=self.github_compat + ) except AuthorizeError as error: LOGGER.warning(error.description, redirect_uri=error.redirect_uri) raise RequestValidationError(error.get_response(self.request)) @@ -402,7 +437,7 @@ class AuthorizationFlowInitView(PolicyAccessView): # OpenID clients can specify a `prompt` parameter, and if its set to consent we # need to inject a consent stage if PROMPT_CONSENT in self.params.prompt: - if not any(isinstance(x.stage, ConsentStageView) for x in plan.bindings): + if not any(isinstance(x.stage, ConsentStage) for x in plan.bindings): # Plan does not have any consent stage, so we add an in-memory one stage = ConsentStage( name="OAuth2 Provider In-memory consent stage", diff --git a/authentik/providers/oauth2/views/token.py b/authentik/providers/oauth2/views/token.py index 93578d6ce..3bf134b09 100644 --- a/authentik/providers/oauth2/views/token.py +++ b/authentik/providers/oauth2/views/token.py @@ -41,6 +41,7 @@ from authentik.providers.oauth2.constants import ( GRANT_TYPE_PASSWORD, GRANT_TYPE_REFRESH_TOKEN, PKCE_METHOD_S256, + SCOPE_OFFLINE_ACCESS, TOKEN_TYPE, ) from authentik.providers.oauth2.errors import DeviceCodeError, TokenError, UserAuthError @@ -459,7 +460,7 @@ class TokenView(View): op="authentik.providers.oauth2.post.response", ): if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE: - LOGGER.debug("Converting authorization code to refresh token") + LOGGER.debug("Converting authorization code to access token") return TokenResponse(self.create_code_response()) if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN: LOGGER.debug("Refreshing refresh token") @@ -489,49 +490,56 @@ class TokenView(View): auth_time=self.params.authorization_code.auth_time, session_id=self.params.authorization_code.session_id, ) - access_token.id_token = IDToken.new( + access_id_token = IDToken.new( self.provider, access_token, self.request, ) + access_id_token.nonce = self.params.authorization_code.nonce + access_token.id_token = access_id_token access_token.save() - refresh_token_expiry = now + timedelta_from_string(self.provider.refresh_token_validity) - refresh_token = RefreshToken( - user=self.params.authorization_code.user, - scope=self.params.authorization_code.scope, - expires=refresh_token_expiry, - provider=self.provider, - auth_time=self.params.authorization_code.auth_time, - session_id=self.params.authorization_code.session_id, - ) - id_token = IDToken.new( - self.provider, - refresh_token, - self.request, - ) - id_token.nonce = self.params.authorization_code.nonce - id_token.at_hash = access_token.at_hash - refresh_token.id_token = id_token - refresh_token.save() - - # Delete old code - self.params.authorization_code.delete() - return { + response = { "access_token": access_token.token, - "refresh_token": refresh_token.token, "token_type": TOKEN_TYPE, "expires_in": int( timedelta_from_string(self.provider.access_token_validity).total_seconds() ), - "id_token": id_token.to_jwt(self.provider), + "id_token": access_token.id_token.to_jwt(self.provider), } + if SCOPE_OFFLINE_ACCESS in self.params.authorization_code.scope: + refresh_token_expiry = now + timedelta_from_string(self.provider.refresh_token_validity) + refresh_token = RefreshToken( + user=self.params.authorization_code.user, + scope=self.params.authorization_code.scope, + expires=refresh_token_expiry, + provider=self.provider, + auth_time=self.params.authorization_code.auth_time, + session_id=self.params.authorization_code.session_id, + ) + id_token = IDToken.new( + self.provider, + refresh_token, + self.request, + ) + id_token.nonce = self.params.authorization_code.nonce + id_token.at_hash = access_token.at_hash + refresh_token.id_token = id_token + refresh_token.save() + response["refresh_token"] = refresh_token.token + + # Delete old code + self.params.authorization_code.delete() + return response + def create_refresh_response(self) -> dict[str, Any]: """See https://datatracker.ietf.org/doc/html/rfc6749#section-6""" unauthorized_scopes = set(self.params.scope) - set(self.params.refresh_token.scope) if unauthorized_scopes: raise TokenError("invalid_scope") + if SCOPE_OFFLINE_ACCESS not in self.params.scope: + raise TokenError("invalid_scope") now = timezone.now() access_token_expiry = now + timedelta_from_string(self.provider.access_token_validity) access_token = AccessToken( @@ -630,31 +638,34 @@ class TokenView(View): ) access_token.save() - refresh_token_expiry = now + timedelta_from_string(self.provider.refresh_token_validity) - refresh_token = RefreshToken( - user=self.params.device_code.user, - scope=self.params.device_code.scope, - expires=refresh_token_expiry, - provider=self.provider, - auth_time=auth_event.created if auth_event else now, - ) - id_token = IDToken.new( - self.provider, - refresh_token, - self.request, - ) - id_token.at_hash = access_token.at_hash - refresh_token.id_token = id_token - refresh_token.save() - - # Delete device code - self.params.device_code.delete() - return { + response = { "access_token": access_token.token, - "refresh_token": refresh_token.token, "token_type": TOKEN_TYPE, "expires_in": int( timedelta_from_string(self.provider.access_token_validity).total_seconds() ), - "id_token": id_token.to_jwt(self.provider), + "id_token": access_token.id_token.to_jwt(self.provider), } + + if SCOPE_OFFLINE_ACCESS in self.params.scope: + refresh_token_expiry = now + timedelta_from_string(self.provider.refresh_token_validity) + refresh_token = RefreshToken( + user=self.params.device_code.user, + scope=self.params.device_code.scope, + expires=refresh_token_expiry, + provider=self.provider, + auth_time=auth_event.created if auth_event else now, + ) + id_token = IDToken.new( + self.provider, + refresh_token, + self.request, + ) + id_token.at_hash = access_token.at_hash + refresh_token.id_token = id_token + refresh_token.save() + response["refresh_token"] = refresh_token.token + + # Delete device code + self.params.device_code.delete() + return response diff --git a/authentik/rbac/api/rbac_roles.py b/authentik/rbac/api/rbac_roles.py index 1c48169a2..1ef13c115 100644 --- a/authentik/rbac/api/rbac_roles.py +++ b/authentik/rbac/api/rbac_roles.py @@ -24,7 +24,10 @@ class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer): def get_app_label_verbose(self, instance: GroupObjectPermission) -> str: """Get app label from permission's model""" - return apps.get_app_config(instance.content_type.app_label).verbose_name + try: + return apps.get_app_config(instance.content_type.app_label).verbose_name + except LookupError: + return instance.content_type.app_label def get_model_verbose(self, instance: GroupObjectPermission) -> str: """Get model label from permission's model""" diff --git a/authentik/rbac/api/rbac_users.py b/authentik/rbac/api/rbac_users.py index 636b327f3..6de2e8bce 100644 --- a/authentik/rbac/api/rbac_users.py +++ b/authentik/rbac/api/rbac_users.py @@ -24,7 +24,10 @@ class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer): def get_app_label_verbose(self, instance: UserObjectPermission) -> str: """Get app label from permission's model""" - return apps.get_app_config(instance.content_type.app_label).verbose_name + try: + return apps.get_app_config(instance.content_type.app_label).verbose_name + except LookupError: + return instance.content_type.app_label def get_model_verbose(self, instance: UserObjectPermission) -> str: """Get model label from permission's model""" diff --git a/authentik/stages/user_login/middleware.py b/authentik/stages/user_login/middleware.py index 8fea4c408..73e42e1ac 100644 --- a/authentik/stages/user_login/middleware.py +++ b/authentik/stages/user_login/middleware.py @@ -109,7 +109,10 @@ class BoundSessionMiddleware(SessionMiddleware): self.recheck_session_geo(configured_binding_geo, last_ip, new_ip) # If we got to this point without any error being raised, we need to # update the last saved IP to the current one - request.session[SESSION_KEY_LAST_IP] = new_ip + if SESSION_KEY_BINDING_NET in request.session or SESSION_KEY_BINDING_GEO in request.session: + # Only set the last IP in the session if there's a binding specified + # (== basically requires the user to be logged in) + request.session[SESSION_KEY_LAST_IP] = new_ip AuthenticatedSession.objects.filter(session_key=request.session.session_key).update( last_ip=new_ip, last_user_agent=request.META.get("HTTP_USER_AGENT", "") ) diff --git a/blueprints/schema.json b/blueprints/schema.json index a0643ea06..e36e674c6 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -9011,6 +9011,12 @@ "prompt" ], "title": "Auth mode" + }, + "maximum_connections": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "title": "Maximum connections" } }, "required": [] diff --git a/blueprints/system/providers-oauth2.yaml b/blueprints/system/providers-oauth2.yaml index eb4ca5442..0d2448202 100644 --- a/blueprints/system/providers-oauth2.yaml +++ b/blueprints/system/providers-oauth2.yaml @@ -45,3 +45,14 @@ entries: # groups is not part of the official userinfo schema, but is a quasi-standard "groups": [group.name for group in request.user.ak_groups.all()], } + - identifiers: + managed: goauthentik.io/providers/oauth2/scope-offline_access + model: authentik_providers_oauth2.scopemapping + attrs: + name: "authentik default OAuth Mapping: OpenID 'offline_access'" + scope_name: offline_access + description: "Access to request new tokens without interaction" + expression: | + # This scope grants the application a refresh token that can be used to refresh user data + # and let the application access authentik without the users interaction + return {} diff --git a/go.mod b/go.mod index ed9759796..5b08a4a39 100644 --- a/go.mod +++ b/go.mod @@ -23,15 +23,15 @@ require ( github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 github.com/pires/go-proxyproto v0.7.0 github.com/prometheus/client_golang v1.18.0 - github.com/redis/go-redis/v9 v9.3.1 + github.com/redis/go-redis/v9 v9.4.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 github.com/wwt/guac v1.3.2 - goauthentik.io/api/v3 v3.2023105.3 + goauthentik.io/api/v3 v3.2023105.5 golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab - golang.org/x/oauth2 v0.15.0 - golang.org/x/sync v0.5.0 + golang.org/x/oauth2 v0.16.0 + golang.org/x/sync v0.6.0 gopkg.in/yaml.v2 v2.4.0 layeh.com/radius v0.0.0-20210819152912-ad72663a72ab ) @@ -74,9 +74,9 @@ require ( go.opentelemetry.io/otel v1.17.0 // indirect go.opentelemetry.io/otel/metric v1.17.0 // indirect go.opentelemetry.io/otel/trace v1.17.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index 3000d744b..c6ecdaf37 100644 --- a/go.sum +++ b/go.sum @@ -258,8 +258,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds= -github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= +github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= @@ -316,8 +316,8 @@ go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYO go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -goauthentik.io/api/v3 v3.2023105.3 h1:x0pMJIKkbN198OOssqA94h8bO6ft9gwG8bpZqZL7WVg= -goauthentik.io/api/v3 v3.2023105.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= +goauthentik.io/api/v3 v3.2023105.5 h1:wIL3Q0jry1g4kRWpH/Dv1sQqhzuL4BLC+uP/Tar1P/g= +goauthentik.io/api/v3 v3.2023105.5/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= @@ -327,8 +327,8 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -394,16 +394,16 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -415,8 +415,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -452,8 +452,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 9fb05420d..9da4a16e2 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-03 11:22+0000\n" +"POT-Creation-Date: 2024-01-09 15:37+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -92,11 +92,11 @@ msgstr "" msgid "Brands" msgstr "" -#: authentik/core/api/providers.py:120 +#: authentik/core/api/providers.py:122 msgid "SAML Provider from Metadata" msgstr "" -#: authentik/core/api/providers.py:121 +#: authentik/core/api/providers.py:123 msgid "Create a SAML Provider by importing its Metadata." msgstr "" @@ -343,7 +343,7 @@ msgid "Powered by authentik" msgstr "" #: authentik/core/views/apps.py:53 -#: authentik/providers/oauth2/views/authorize.py:395 +#: authentik/providers/oauth2/views/authorize.py:430 #: authentik/providers/oauth2/views/device_init.py:70 #: authentik/providers/saml/views/sso.py:70 #, python-format @@ -372,6 +372,10 @@ msgstr "" msgid "Certificate-Key Pairs" msgstr "" +#: authentik/enterprise/api.py:33 +msgid "Enterprise is required to create/update this object." +msgstr "" + #: authentik/enterprise/models.py:183 msgid "License" msgstr "" @@ -411,22 +415,30 @@ msgstr "" msgid "RAC Providers" msgstr "" -#: authentik/enterprise/providers/rac/models.py:99 +#: authentik/enterprise/providers/rac/models.py:100 msgid "RAC Endpoint" msgstr "" -#: authentik/enterprise/providers/rac/models.py:100 +#: authentik/enterprise/providers/rac/models.py:101 msgid "RAC Endpoints" msgstr "" -#: authentik/enterprise/providers/rac/models.py:121 +#: authentik/enterprise/providers/rac/models.py:122 msgid "RAC Property Mapping" msgstr "" -#: authentik/enterprise/providers/rac/models.py:122 +#: authentik/enterprise/providers/rac/models.py:123 msgid "RAC Property Mappings" msgstr "" +#: authentik/enterprise/providers/rac/views.py:103 +msgid "Maximum connection limit reached." +msgstr "" + +#: authentik/enterprise/providers/rac/views.py:107 +msgid "(You are already connected in another tab/window)" +msgstr "" + #: authentik/events/models.py:289 msgid "Event" msgstr "" @@ -1282,7 +1294,7 @@ msgstr "" msgid "Device Tokens" msgstr "" -#: authentik/providers/oauth2/views/authorize.py:450 +#: authentik/providers/oauth2/views/authorize.py:485 #: authentik/providers/saml/views/flows.py:87 #, python-format msgid "Redirecting to %(app)s..." diff --git a/schema.yml b/schema.yml index 5d03ab5d9..7cd7718ef 100644 --- a/schema.yml +++ b/schema.yml @@ -19247,6 +19247,7 @@ paths: - tr - tt - udm + - ug - uk - ur - uz @@ -32108,6 +32109,10 @@ components: Build actual launch URL (the provider itself does not have one, just individual endpoints) readOnly: true + maximum_connections: + type: integer + maximum: 2147483647 + minimum: -2147483648 required: - auth_mode - host @@ -32139,6 +32144,10 @@ components: format: uuid auth_mode: $ref: '#/components/schemas/AuthModeEnum' + maximum_connections: + type: integer + maximum: 2147483647 + minimum: -2147483648 required: - auth_mode - host @@ -38118,6 +38127,10 @@ components: format: uuid auth_mode: $ref: '#/components/schemas/AuthModeEnum' + maximum_connections: + type: integer + maximum: 2147483647 + minimum: -2147483648 PatchedEventMatcherPolicyRequest: type: object description: Event Matcher Policy Serializer @@ -43786,6 +43799,9 @@ components: type: string model_name: type: string + requires_enterprise: + type: boolean + default: false required: - component - description diff --git a/tests/e2e/test_provider_oauth2_github.py b/tests/e2e/test_provider_oauth2_github.py index 5e19dd146..5095421a8 100644 --- a/tests/e2e/test_provider_oauth2_github.py +++ b/tests/e2e/test_provider_oauth2_github.py @@ -74,7 +74,7 @@ class TestProviderOAuth2Github(SeleniumTestCase): slug="default-provider-authorization-implicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), client_id=self.client_id, client_secret=self.client_secret, client_type=ClientTypes.CONFIDENTIAL, @@ -82,8 +82,8 @@ class TestProviderOAuth2Github(SeleniumTestCase): authorization_flow=authorization_flow, ) Application.objects.create( - name="Grafana", - slug="grafana", + name=generate_id(), + slug=generate_id(), provider=provider, ) @@ -129,7 +129,7 @@ class TestProviderOAuth2Github(SeleniumTestCase): slug="default-provider-authorization-explicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), client_id=self.client_id, client_secret=self.client_secret, client_type=ClientTypes.CONFIDENTIAL, @@ -137,8 +137,8 @@ class TestProviderOAuth2Github(SeleniumTestCase): authorization_flow=authorization_flow, ) app = Application.objects.create( - name="Grafana", - slug="grafana", + name=generate_id(), + slug=generate_id(), provider=provider, ) @@ -200,7 +200,7 @@ class TestProviderOAuth2Github(SeleniumTestCase): slug="default-provider-authorization-explicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), client_id=self.client_id, client_secret=self.client_secret, client_type=ClientTypes.CONFIDENTIAL, @@ -208,8 +208,8 @@ class TestProviderOAuth2Github(SeleniumTestCase): authorization_flow=authorization_flow, ) app = Application.objects.create( - name="Grafana", - slug="grafana", + name=generate_id(), + slug=generate_id(), provider=provider, ) diff --git a/tests/e2e/test_provider_oauth2_grafana.py b/tests/e2e/test_provider_oauth2_grafana.py index 2538fae70..fa8f12d1d 100644 --- a/tests/e2e/test_provider_oauth2_grafana.py +++ b/tests/e2e/test_provider_oauth2_grafana.py @@ -14,6 +14,7 @@ from authentik.lib.generators import generate_id, generate_key from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.models import PolicyBinding from authentik.providers.oauth2.constants import ( + SCOPE_OFFLINE_ACCESS, SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE, @@ -80,7 +81,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): slug="default-provider-authorization-implicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), client_type=ClientTypes.CONFIDENTIAL, client_id=self.client_id, client_secret=self.client_secret, @@ -90,12 +91,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() Application.objects.create( - name="Grafana", + name=generate_id(), slug=self.app_slug, provider=provider, ) @@ -113,12 +119,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-implicit-consent.yaml", - ) - @apply_blueprint( - "system/providers-oauth2.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml") + @apply_blueprint("system/providers-oauth2.yaml") @reconcile_app("authentik_crypto") def test_authorization_consent_implied(self): """test OpenID Provider flow (default authorization flow with implied consent)""" @@ -128,7 +130,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): slug="default-provider-authorization-implicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), client_type=ClientTypes.CONFIDENTIAL, client_id=self.client_id, client_secret=self.client_secret, @@ -138,11 +140,16 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) Application.objects.create( - name="Grafana", + name=generate_id(), slug=self.app_slug, provider=provider, ) @@ -174,12 +181,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-implicit-consent.yaml", - ) - @apply_blueprint( - "system/providers-oauth2.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml") + @apply_blueprint("system/providers-oauth2.yaml") @reconcile_app("authentik_crypto") def test_authorization_logout(self): """test OpenID Provider flow with logout""" @@ -189,7 +192,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): slug="default-provider-authorization-implicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), client_type=ClientTypes.CONFIDENTIAL, client_id=self.client_id, client_secret=self.client_secret, @@ -199,12 +202,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() Application.objects.create( - name="Grafana", + name=generate_id(), slug=self.app_slug, provider=provider, ) @@ -244,12 +252,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-explicit-consent.yaml", - ) - @apply_blueprint( - "system/providers-oauth2.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml") + @apply_blueprint("system/providers-oauth2.yaml") @reconcile_app("authentik_crypto") def test_authorization_consent_explicit(self): """test OpenID Provider flow (default authorization flow with explicit consent)""" @@ -259,7 +263,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): slug="default-provider-authorization-explicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), authorization_flow=authorization_flow, client_type=ClientTypes.CONFIDENTIAL, client_id=self.client_id, @@ -269,12 +273,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() app = Application.objects.create( - name="Grafana", + name=generate_id(), slug=self.app_slug, provider=provider, ) @@ -323,12 +332,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-explicit-consent.yaml", - ) - @apply_blueprint( - "system/providers-oauth2.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml") + @apply_blueprint("system/providers-oauth2.yaml") @reconcile_app("authentik_crypto") def test_authorization_denied(self): """test OpenID Provider flow (default authorization with access deny)""" @@ -338,7 +343,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): slug="default-provider-authorization-explicit-consent" ) provider = OAuth2Provider.objects.create( - name="grafana", + name=generate_id(), authorization_flow=authorization_flow, client_type=ClientTypes.CONFIDENTIAL, client_id=self.client_id, @@ -348,12 +353,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() app = Application.objects.create( - name="Grafana", + name=generate_id(), slug=self.app_slug, provider=provider, ) diff --git a/tests/e2e/test_provider_oidc.py b/tests/e2e/test_provider_oidc.py index 3180f9534..c14a498a6 100644 --- a/tests/e2e/test_provider_oidc.py +++ b/tests/e2e/test_provider_oidc.py @@ -15,6 +15,7 @@ from authentik.lib.generators import generate_id, generate_key from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.models import PolicyBinding from authentik.providers.oauth2.constants import ( + SCOPE_OFFLINE_ACCESS, SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE, @@ -29,7 +30,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): def setUp(self): self.client_id = generate_id() self.client_secret = generate_key() - self.application_slug = "test" + self.application_slug = generate_id() super().setUp() def setup_client(self) -> Container: @@ -37,7 +38,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): sleep(1) client: DockerClient = from_env() container = client.containers.run( - image="ghcr.io/beryju/oidc-test-client:1.3", + image="ghcr.io/beryju/oidc-test-client:2.1", detach=True, ports={ "9009": "9009", @@ -56,9 +57,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-implicit-consent.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml") @reconcile_app("authentik_crypto") def test_redirect_uri_error(self): """test OpenID Provider flow (invalid redirect URI, check error message)""" @@ -78,10 +77,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) - provider.save() Application.objects.create( name=self.application_slug, slug=self.application_slug, @@ -101,13 +104,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-implicit-consent.yaml", - ) - @reconcile_app("authentik_crypto") + @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml") @apply_blueprint("system/providers-oauth2.yaml") + @reconcile_app("authentik_crypto") def test_authorization_consent_implied(self): - """test OpenID Provider flow (default authorization flow with implied consent)""" + """test OpenID Provider flow (default authorization flow with implied consent) + (due to offline_access a consent will still be triggered)""" sleep(1) # Bootstrap all needed objects authorization_flow = Flow.objects.get( @@ -124,11 +126,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) - provider.save() - Application.objects.create( + app = Application.objects.create( name=self.application_slug, slug=self.application_slug, provider=provider, @@ -137,6 +143,20 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): self.driver.get("http://localhost:9009") self.login() + self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-flow-executor"))) + + flow_executor = self.get_shadow_root("ak-flow-executor") + consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor) + + self.assertIn( + app.name, + consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + ) + consent_stage.find_element( + By.CSS_SELECTOR, + "[type=submit]", + ).click() + self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre"))) self.wait.until(ec.text_to_be_present_in_element((By.CSS_SELECTOR, "pre"), "{")) body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) @@ -155,11 +175,9 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-explicit-consent.yaml", - ) - @reconcile_app("authentik_crypto") + @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml") @apply_blueprint("system/providers-oauth2.yaml") + @reconcile_app("authentik_crypto") def test_authorization_consent_explicit(self): """test OpenID Provider flow (default authorization flow with explicit consent)""" sleep(1) @@ -178,10 +196,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) - provider.save() app = Application.objects.create( name=self.application_slug, slug=self.application_slug, @@ -224,9 +246,8 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-explicit-consent.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml") + @apply_blueprint("system/providers-oauth2.yaml") @reconcile_app("authentik_crypto") def test_authorization_denied(self): """test OpenID Provider flow (default authorization with access deny)""" @@ -246,10 +267,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) - provider.save() app = Application.objects.create( name=self.application_slug, slug=self.application_slug, diff --git a/tests/e2e/test_provider_oidc_implicit.py b/tests/e2e/test_provider_oidc_implicit.py index c5d9d37d0..37dff0aa3 100644 --- a/tests/e2e/test_provider_oidc_implicit.py +++ b/tests/e2e/test_provider_oidc_implicit.py @@ -15,6 +15,7 @@ from authentik.lib.generators import generate_id, generate_key from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.models import PolicyBinding from authentik.providers.oauth2.constants import ( + SCOPE_OFFLINE_ACCESS, SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE, @@ -37,7 +38,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): sleep(1) client: DockerClient = from_env() container = client.containers.run( - image="ghcr.io/beryju/oidc-test-client:1.3", + image="ghcr.io/beryju/oidc-test-client:2.1", detach=True, ports={ "9009": "9009", @@ -56,9 +57,8 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-implicit-consent.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml") + @apply_blueprint("system/providers-oauth2.yaml") @reconcile_app("authentik_crypto") def test_redirect_uri_error(self): """test OpenID Provider flow (invalid redirect URI, check error message)""" @@ -78,7 +78,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() @@ -101,11 +106,9 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-implicit-consent.yaml", - ) - @reconcile_app("authentik_crypto") + @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml") @apply_blueprint("system/providers-oauth2.yaml") + @reconcile_app("authentik_crypto") def test_authorization_consent_implied(self): """test OpenID Provider flow (default authorization flow with implied consent)""" sleep(1) @@ -124,7 +127,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() @@ -150,11 +158,9 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-explicit-consent.yaml", - ) - @reconcile_app("authentik_crypto") + @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml") @apply_blueprint("system/providers-oauth2.yaml") + @reconcile_app("authentik_crypto") def test_authorization_consent_explicit(self): """test OpenID Provider flow (default authorization flow with explicit consent)""" sleep(1) @@ -173,7 +179,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() @@ -215,9 +226,8 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): "default/flow-default-authentication-flow.yaml", "default/flow-default-invalidation-flow.yaml", ) - @apply_blueprint( - "default/flow-default-provider-authorization-explicit-consent.yaml", - ) + @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml") + @apply_blueprint("system/providers-oauth2.yaml") @reconcile_app("authentik_crypto") def test_authorization_denied(self): """test OpenID Provider flow (default authorization with access deny)""" @@ -237,7 +247,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): ) provider.property_mappings.set( ScopeMapping.objects.filter( - scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE] + scope_name__in=[ + SCOPE_OPENID, + SCOPE_OPENID_EMAIL, + SCOPE_OPENID_PROFILE, + SCOPE_OFFLINE_ACCESS, + ] ) ) provider.save() diff --git a/tests/wdio/package-lock.json b/tests/wdio/package-lock.json index 3d56efbe1..6ab7d4717 100644 --- a/tests/wdio/package-lock.json +++ b/tests/wdio/package-lock.json @@ -7,8 +7,8 @@ "name": "@goauthentik/web-tests", "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "@wdio/cli": "^8.27.1", "@wdio/local-runner": "^8.27.0", "@wdio/mocha-framework": "^8.27.0", @@ -946,16 +946,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", - "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", + "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/type-utils": "6.17.0", - "@typescript-eslint/utils": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/type-utils": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -981,15 +981,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", - "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", + "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4" }, "engines": { @@ -1009,13 +1009,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", - "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0" + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1026,13 +1026,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", - "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", + "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/utils": "6.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1053,9 +1053,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1066,13 +1066,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", - "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1118,17 +1118,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", - "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", + "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", "semver": "^7.5.4" }, "engines": { @@ -1143,12 +1143,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.18.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { diff --git a/tests/wdio/package.json b/tests/wdio/package.json index 768dabac3..a55a8012f 100644 --- a/tests/wdio/package.json +++ b/tests/wdio/package.json @@ -4,8 +4,8 @@ "type": "module", "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "@wdio/cli": "^8.27.1", "@wdio/local-runner": "^8.27.0", "@wdio/mocha-framework": "^8.27.0", diff --git a/web/README.md b/web/README.md index 24cc5c622..9a8c1c846 100644 --- a/web/README.md +++ b/web/README.md @@ -3,6 +3,92 @@ This is the default UI for the authentik server. The documentation is going to be a little sparse for awhile, but at least let's get started. +# The Theory of the authentik UI + +In Peter Naur's 1985 essay [Programming as Theory +Building](https://pages.cs.wisc.edu/~remzi/Naur.pdf), programming is described as creating a mental +model of how a program _should_ run, then writing the code to test if the program _can_ run that +way. + +The mental model for the authentik UI is straightforward. There are five "applications" within the +UI, each with its own base URL, router, and responsibilities, and each application needs as many as +three contexts in which to run. + +The three contexts corresponds to objects in the API's `model` section, so let's use those names. + +- The root `Config`. The root configuration object of the server, containing mostly caching and + error reporting information. This is misleading, however; the `Config` object contains some user + information, specifically a list of permissions the current user (or "no user") has. +- The root `CurrentTenant`. This describes the `Brand` information UIs should use, such as themes, + logos, favicon, and specific default flows for logging in, logging out, and recovering a user + password. +- The current `SessionUser`, the person logged in: username, display name, and various states. + (Note: the authentik server permits administrators to "impersonate" any other user in order to + debug their authentikation experience. If impersonation is active, the `user` field reflects that + user, but it also includes a field, `original`, with the administrator's information.) + +(There is a fourth context object, Version, but its use is limited to displaying version information +and checking for upgrades. Just be aware that you will see it, but you will probably never interact +with it.) + +There are five applications. Two (`loading` and `api-browser`) are trivial applications whose +insides are provided by third-party libraries (Patternfly and Rapidoc, respectively). The other +three are actual applications. The descriptions below are wholly from the view of the user's +experience: + +- `Flow`: From a given URL, displays a form that requests information from the user to accomplish a + task. Some tasks require the user to be logged in, but many (such as logging in itself!) + obviously do not. +- `User`: Provides the user with access to the applications they can access, plus a few user + settings. +- `Admin`: Provides someone with super-user permissions access to the administrative functions of + the authentik server. + +**Mental Model** + +- Upon initialization, _every_ authentik UI application fetches `Config` and `CurrentTenant`. `User` + and `Admin` will also attempt to load the `SessionUser`; if there is none, the user is kicked out + to the `Flow` for logging into authentik itself. +- `Config`, `CurrentTenant`, and `SessionUser`, are provided by the `@goauthentik/api` application, + not by the codebase under `./web`. (Where you are now). +- `Flow`, `User`, and `Admin` are all called `Interfaces` and are found in + `./web/src/flow/FlowInterface`, `./web/src/user/UserInterface`, `./web/src/admin/AdminInterface`, + respectively. + +Inside each of these you will find, in a hierarchal order: + +- The context layer described above + - A theme managing layer + - The orchestration layer: + - web socket handler for server-generated events + - The router + - Individual routes for each vertical slice and its relationship to other objects: + +Each slice corresponds to an object table on the server, and each slice _usually_ consists of the +following: + +- A paginated collection display, usually using the `Table` foundation (found in + `./web/src/elements/Table`) +- The ability to view an individual object from the collection, which you may be able to: + - Edit + - Delete +- A form for creating a new object +- Tabs showing that object's relationship to other objects + - Interactive elements for changing or deleting those relationships, or creating new ones. + - The ability to create new objects with which to have that relationship, if they're not part of + the core objects (such as User->MFA authenticator apps, since the latter is not a "core" object + and has no tab of its own). + +We are still a bit "all over the place" with respect to sub-units and common units; there are +folders `common`, `elements`, and `components`, and ideally they would be: + +- `common`: non-UI related libraries all of our applications need +- `elements`: UI elements shared among multiple applications that do not need context +- `components`: UI elements shared among multiple that use one or more context + +... but at the moment there are some context-sensitive elements, and some UI-related stuff in +`common`. + # Comments **NOTE:** The comments in this section are for specific changes to this repository that cannot be diff --git a/web/lit-localize.json b/web/lit-localize.json index 19a901a91..3c9564de8 100644 --- a/web/lit-localize.json +++ b/web/lit-localize.json @@ -3,15 +3,18 @@ "sourceLocale": "en", "targetLocales": [ "en", - "pseudo-LOCALE", - "fr", - "tr", + "de", "es", + "fr", + "ko", + "nl", "pl", - "zh_TW", + "tr", "zh-Hans", "zh-Hant", - "de" + "zh-CN", + "zh_TW", + "pseudo-LOCALE" ], "tsConfig": "./tsconfig.json", "output": { diff --git a/web/package-lock.json b/web/package-lock.json index d6305b661..6cab57f36 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -17,15 +17,15 @@ "@codemirror/theme-one-dark": "^6.1.2", "@formatjs/intl-listformat": "^7.5.3", "@fortawesome/fontawesome-free": "^6.5.1", - "@goauthentik/api": "^2023.10.5-1703968412", + "@goauthentik/api": "^2023.10.5-1704382057", "@lit-labs/context": "^0.4.0", "@lit-labs/task": "^3.1.0", "@lit/localize": "^0.11.4", "@open-wc/lit-helpers": "^0.6.0", "@patternfly/elements": "^2.4.0", "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.91.0", - "@sentry/tracing": "^7.91.0", + "@sentry/browser": "^7.92.0", + "@sentry/tracing": "^7.92.0", "@webcomponents/webcomponentsjs": "^2.8.0", "base64-js": "^1.5.1", "chart.js": "^4.4.1", @@ -50,7 +50,7 @@ "@babel/plugin-transform-private-methods": "^7.23.3", "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-runtime": "^7.23.7", - "@babel/preset-env": "^7.23.7", + "@babel/preset-env": "^7.23.8", "@babel/preset-typescript": "^7.23.3", "@hcaptcha/types": "^1.0.3", "@jackfranklin/rollup-plugin-markdown": "^0.4.0", @@ -74,8 +74,8 @@ "@types/codemirror": "5.60.15", "@types/grecaptcha": "^3.0.7", "@types/guacamole-common-js": "1.5.2", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "babel-plugin-macros": "^3.1.0", "babel-plugin-tsconfig-paths": "^1.0.3", "cross-env": "^7.0.3", @@ -85,21 +85,21 @@ "eslint-plugin-lit": "^1.11.0", "eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-storybook": "^0.6.15", - "lit-analyzer": "^2.0.2", + "lit-analyzer": "^2.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.1.1", "pseudolocale": "^2.0.0", "pyright": "=1.1.338", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.9.2", + "rollup": "^4.9.4", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-modify": "^3.0.0", "rollup-plugin-postcss-lit": "^2.1.0", "storybook": "^7.6.7", "storybook-addon-mock": "^4.3.0", - "ts-lit-plugin": "^2.0.1", + "ts-lit-plugin": "^2.0.2", "tslib": "^2.6.2", "turnstile-types": "^1.2.0", "typescript": "^5.3.3", @@ -1119,16 +1119,15 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", - "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", + "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", @@ -1833,9 +1832,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", - "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz", + "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", @@ -1871,7 +1870,7 @@ "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-classes": "^7.23.8", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", @@ -2913,9 +2912,9 @@ } }, "node_modules/@goauthentik/api": { - "version": "2023.10.5-1703968412", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.5-1703968412.tgz", - "integrity": "sha512-/2QDgGkWGXOYDqH49/2hNs+U8TqdE94hkMrJc8A6L+NAy8x/zKAY39eUHs85jmwt013N5duD/jKiJsRftHsDig==" + "version": "2023.10.5-1704382057", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.5-1704382057.tgz", + "integrity": "sha512-nzmAQgTrFXiOwKDeHhq1gAfIMRilAcDPmNvjhqoQc3GQfWs5GG2lAGzIewWyxsfxNABsg+I0BYTPxN7ffD6tXw==" }, "node_modules/@hcaptcha/types": { "version": "1.0.3", @@ -4582,9 +4581,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.2.tgz", - "integrity": "sha512-RKzxFxBHq9ysZ83fn8Iduv3A283K7zPPYuhL/z9CQuyFrjwpErJx0h4aeb/bnJ+q29GRLgJpY66ceQ/Wcsn3wA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz", + "integrity": "sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==", "cpu": [ "arm" ], @@ -4595,9 +4594,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.2.tgz", - "integrity": "sha512-yZ+MUbnwf3SHNWQKJyWh88ii2HbuHCFQnAYTeeO1Nb8SyEiWASEi5dQUygt3ClHWtA9My9RQAYkjvrsZ0WK8Xg==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz", + "integrity": "sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==", "cpu": [ "arm64" ], @@ -4608,9 +4607,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.2.tgz", - "integrity": "sha512-vqJ/pAUh95FLc/G/3+xPqlSBgilPnauVf2EXOQCZzhZJCXDXt/5A8mH/OzU6iWhb3CNk5hPJrh8pqJUPldN5zw==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz", + "integrity": "sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==", "cpu": [ "arm64" ], @@ -4621,9 +4620,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.2.tgz", - "integrity": "sha512-otPHsN5LlvedOprd3SdfrRNhOahhVBwJpepVKUN58L0RnC29vOAej1vMEaVU6DadnpjivVsNTM5eNt0CcwTahw==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz", + "integrity": "sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==", "cpu": [ "x64" ], @@ -4634,9 +4633,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.2.tgz", - "integrity": "sha512-ewG5yJSp+zYKBYQLbd1CUA7b1lSfIdo9zJShNTyc2ZP1rcPrqyZcNlsHgs7v1zhgfdS+kW0p5frc0aVqhZCiYQ==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz", + "integrity": "sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==", "cpu": [ "arm" ], @@ -4647,9 +4646,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.2.tgz", - "integrity": "sha512-pL6QtV26W52aCWTG1IuFV3FMPL1m4wbsRG+qijIvgFO/VBsiXJjDPE/uiMdHBAO6YcpV4KvpKtd0v3WFbaxBtg==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz", + "integrity": "sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==", "cpu": [ "arm64" ], @@ -4660,9 +4659,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.2.tgz", - "integrity": "sha512-On+cc5EpOaTwPSNetHXBuqylDW+765G/oqB9xGmWU3npEhCh8xu0xqHGUA+4xwZLqBbIZNcBlKSIYfkBm6ko7g==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz", + "integrity": "sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==", "cpu": [ "arm64" ], @@ -4673,9 +4672,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.2.tgz", - "integrity": "sha512-Wnx/IVMSZ31D/cO9HSsU46FjrPWHqtdF8+0eyZ1zIB5a6hXaZXghUKpRrC4D5DcRTZOjml2oBhXoqfGYyXKipw==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz", + "integrity": "sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==", "cpu": [ "riscv64" ], @@ -4686,9 +4685,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.2.tgz", - "integrity": "sha512-ym5x1cj4mUAMBummxxRkI4pG5Vht1QMsJexwGP8547TZ0sox9fCLDHw9KCH9c1FO5d9GopvkaJsBIOkTKxksdw==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz", + "integrity": "sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw==", "cpu": [ "x64" ], @@ -4699,9 +4698,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.2.tgz", - "integrity": "sha512-m0hYELHGXdYx64D6IDDg/1vOJEaiV8f1G/iO+tejvRCJNSwK4jJ15e38JQy5Q6dGkn1M/9KcyEOwqmlZ2kqaZg==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz", + "integrity": "sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==", "cpu": [ "x64" ], @@ -4712,9 +4711,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.2.tgz", - "integrity": "sha512-x1CWburlbN5JjG+juenuNa4KdedBdXLjZMp56nHFSHTOsb/MI2DYiGzLtRGHNMyydPGffGId+VgjOMrcltOksA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz", + "integrity": "sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==", "cpu": [ "arm64" ], @@ -4725,9 +4724,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.2.tgz", - "integrity": "sha512-VVzCB5yXR1QlfsH1Xw1zdzQ4Pxuzv+CPr5qpElpKhVxlxD3CRdfubAG9mJROl6/dmj5gVYDDWk8sC+j9BI9/kQ==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz", + "integrity": "sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==", "cpu": [ "ia32" ], @@ -4738,9 +4737,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.2.tgz", - "integrity": "sha512-SYRedJi+mweatroB+6TTnJYLts0L0bosg531xnQWtklOI6dezEagx4Q0qDyvRdK+qgdA3YZpjjGuPFtxBmddBA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz", + "integrity": "sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==", "cpu": [ "x64" ], @@ -4751,98 +4750,98 @@ ] }, "node_modules/@sentry-internal/feedback": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.91.0.tgz", - "integrity": "sha512-SJKTSaz68F5YIwF79EttBm915M2LnacgZMYRnRumyTmMKnebGhYQLwWbZdpaDvOa1U18dgRajDX8Qed/8A3tXw==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.92.0.tgz", + "integrity": "sha512-/jEALRtVqboxB9kcK2tag8QCO6XANTlGBb9RV3oeGXJe0DDNJXRq6wVZbfgztXJRrfgx4XVDcNt1pRVoGGG++g==", "dependencies": { - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.91.0.tgz", - "integrity": "sha512-JH5y6gs6BS0its7WF2DhySu7nkhPDfZcdpAXldxzIlJpqFkuwQKLU5nkYJpiIyZz1NHYYtW5aum2bV2oCOdDRA==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.92.0.tgz", + "integrity": "sha512-ur55vPcUUUWFUX4eVLNP71ohswK7ZZpleNZw9Y1GfLqyI+0ILQUwjtzqItJrdClvVsdRZJMRmDV40Hp9Lbb9mA==", "dependencies": { - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/browser": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.91.0.tgz", - "integrity": "sha512-lJv3x/xekzC/biiyAsVCioq2XnKNOZhI6jY3ZzLJZClYV8eKRi7D3KCsHRvMiCdGak1d/6sVp8F4NYY+YiWy1Q==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.92.0.tgz", + "integrity": "sha512-loMr02/zQ38u8aQhYLtIBg0i5n3ps2e3GUXrt3CdsJQdkRYfa62gcrE7SzvoEpMVHTk7VOI4fWGht8cWw/1k3A==", "dependencies": { - "@sentry-internal/feedback": "7.91.0", - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/replay": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry-internal/feedback": "7.92.0", + "@sentry-internal/tracing": "7.92.0", + "@sentry/core": "7.92.0", + "@sentry/replay": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/core": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", - "integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.92.0.tgz", + "integrity": "sha512-1Tly7YB2I1byI5xb0Cwrxs56Rhww+6mQ7m9P7rTmdC3/ijOzbEoohtYIUPwcooCEarpbEJe/tAayRx6BrH2UbQ==", "dependencies": { - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/replay": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.91.0.tgz", - "integrity": "sha512-XwbesnLLNtaVXKtDoyBB96GxJuhGi9zy3a662Ba/McmumCnkXrMQYpQPh08U7MgkTyDRgjDwm7PXDhiKpcb03g==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.92.0.tgz", + "integrity": "sha512-G1t9Uvc9cR8VpNkElwvHIMGzykjIKikb10n0tfVd3e+rBPMCCjCPWOduwG6jZYxcvCjTpqmJh6NSLXxL/Mt4JA==", "dependencies": { - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry-internal/tracing": "7.92.0", + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/tracing": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.91.0.tgz", - "integrity": "sha512-IlSAMvqfCL/2TwwN4Tmk6bGMgilGruv5oIJ1GMenVZk53bHwjpjzMbd0ms8+S5zJwAgTQXoCbRhaFFrNmptteQ==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.92.0.tgz", + "integrity": "sha512-1+TFFPVEdax4dNi68gin6MENiyGe9mOuNXfjulrP5eCzUEByus5HAxeDI/LLQ1hArfn048AzwSwKUsS2fO5sbg==", "dependencies": { - "@sentry-internal/tracing": "7.91.0" + "@sentry-internal/tracing": "7.92.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/types": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", - "integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.92.0.tgz", + "integrity": "sha512-APmSOuZuoRGpbPpPeYIbMSplPjiWNLZRQa73QiXuTflW4Tu/ItDlU8hOa2+A6JKVkJCuD2EN6yUrxDGSMyNXeg==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", - "integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.92.0.tgz", + "integrity": "sha512-3nEfrQ1z28b/2zgFGANPh5yMVtgwXmrasZxTvKbrAj+KWJpjrJHrIR84r9W277J44NMeZ5RhRW2uoDmuBslPnA==", "dependencies": { - "@sentry/types": "7.91.0" + "@sentry/types": "7.92.0" }, "engines": { "node": ">=8" @@ -7265,9 +7264,9 @@ "dev": true }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { @@ -7576,16 +7575,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", - "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", + "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/type-utils": "6.17.0", - "@typescript-eslint/utils": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/type-utils": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -7644,15 +7643,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", - "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", + "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4" }, "engines": { @@ -7672,13 +7671,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", - "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0" + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -7689,13 +7688,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", - "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", + "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/utils": "6.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -7716,9 +7715,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -7729,13 +7728,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", - "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -7814,17 +7813,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", - "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", + "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", "semver": "^7.5.4" }, "engines": { @@ -7872,12 +7871,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.18.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -11456,9 +11455,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -13327,9 +13326,9 @@ } }, "node_modules/lit-analyzer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lit-analyzer/-/lit-analyzer-2.0.2.tgz", - "integrity": "sha512-Is3cx8ypCVq5uNl8EKkPdlLuV3HDVntDVUeLNQlzTM2Je3uG5wHcn+06NB+yhCoa4rhwwXCjprU/7g21CSFqOA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lit-analyzer/-/lit-analyzer-2.0.3.tgz", + "integrity": "sha512-XiAjnwVipNrKav7r3CSEZpWt+mwYxrhPRVC7h8knDmn/HWTzzWJvPe+mwBcL2brn4xhItAMzZhFC8tzzqHKmiQ==", "dev": true, "dependencies": { "@vscode/web-custom-data": "^0.4.2", @@ -16419,10 +16418,13 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.2.tgz", - "integrity": "sha512-66RB8OtFKUTozmVEh3qyNfH+b+z2RXBVloqO2KCC/pjFaGaHtxP9fVfOQKPSGXg2mElmjmxjW/fZ7iKrEpMH5Q==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.4.tgz", + "integrity": "sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -16431,19 +16433,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.2", - "@rollup/rollup-android-arm64": "4.9.2", - "@rollup/rollup-darwin-arm64": "4.9.2", - "@rollup/rollup-darwin-x64": "4.9.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.2", - "@rollup/rollup-linux-arm64-gnu": "4.9.2", - "@rollup/rollup-linux-arm64-musl": "4.9.2", - "@rollup/rollup-linux-riscv64-gnu": "4.9.2", - "@rollup/rollup-linux-x64-gnu": "4.9.2", - "@rollup/rollup-linux-x64-musl": "4.9.2", - "@rollup/rollup-win32-arm64-msvc": "4.9.2", - "@rollup/rollup-win32-ia32-msvc": "4.9.2", - "@rollup/rollup-win32-x64-msvc": "4.9.2", + "@rollup/rollup-android-arm-eabi": "4.9.4", + "@rollup/rollup-android-arm64": "4.9.4", + "@rollup/rollup-darwin-arm64": "4.9.4", + "@rollup/rollup-darwin-x64": "4.9.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.4", + "@rollup/rollup-linux-arm64-gnu": "4.9.4", + "@rollup/rollup-linux-arm64-musl": "4.9.4", + "@rollup/rollup-linux-riscv64-gnu": "4.9.4", + "@rollup/rollup-linux-x64-gnu": "4.9.4", + "@rollup/rollup-linux-x64-musl": "4.9.4", + "@rollup/rollup-win32-arm64-msvc": "4.9.4", + "@rollup/rollup-win32-ia32-msvc": "4.9.4", + "@rollup/rollup-win32-x64-msvc": "4.9.4", "fsevents": "~2.3.2" } }, @@ -17835,9 +17837,9 @@ } }, "node_modules/ts-lit-plugin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-lit-plugin/-/ts-lit-plugin-2.0.1.tgz", - "integrity": "sha512-Y5G03aDiMYHMLzoZ50kdeVkzgVig2mBw6PVY2oI9PcWl3ONTcDyYq6rJ0QzhlACYWP8sT0dmaPMsHMObgNNvvg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-lit-plugin/-/ts-lit-plugin-2.0.2.tgz", + "integrity": "sha512-DPXlVxhjWHxg8AyBLcfSYt2JXgpANV1ssxxwjY98o26gD8MzeiM68HFW9c2VeDd1CjoR3w7B/6/uKxwBQe+ioA==", "dev": true, "dependencies": { "lit-analyzer": "^2.0.1", diff --git a/web/package.json b/web/package.json index 92cb66ec9..07a86fdae 100644 --- a/web/package.json +++ b/web/package.json @@ -42,15 +42,15 @@ "@codemirror/theme-one-dark": "^6.1.2", "@formatjs/intl-listformat": "^7.5.3", "@fortawesome/fontawesome-free": "^6.5.1", - "@goauthentik/api": "^2023.10.5-1703968412", + "@goauthentik/api": "^2023.10.5-1704382057", "@lit-labs/context": "^0.4.0", "@lit-labs/task": "^3.1.0", "@lit/localize": "^0.11.4", "@open-wc/lit-helpers": "^0.6.0", "@patternfly/elements": "^2.4.0", "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.91.0", - "@sentry/tracing": "^7.91.0", + "@sentry/browser": "^7.92.0", + "@sentry/tracing": "^7.92.0", "@webcomponents/webcomponentsjs": "^2.8.0", "base64-js": "^1.5.1", "chart.js": "^4.4.1", @@ -75,7 +75,7 @@ "@babel/plugin-transform-private-methods": "^7.23.3", "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-runtime": "^7.23.7", - "@babel/preset-env": "^7.23.7", + "@babel/preset-env": "^7.23.8", "@babel/preset-typescript": "^7.23.3", "@hcaptcha/types": "^1.0.3", "@jackfranklin/rollup-plugin-markdown": "^0.4.0", @@ -99,8 +99,8 @@ "@types/codemirror": "5.60.15", "@types/grecaptcha": "^3.0.7", "@types/guacamole-common-js": "1.5.2", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "babel-plugin-macros": "^3.1.0", "babel-plugin-tsconfig-paths": "^1.0.3", "cross-env": "^7.0.3", @@ -110,21 +110,21 @@ "eslint-plugin-lit": "^1.11.0", "eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-storybook": "^0.6.15", - "lit-analyzer": "^2.0.2", + "lit-analyzer": "^2.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.1.1", "pseudolocale": "^2.0.0", "pyright": "=1.1.338", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.9.2", + "rollup": "^4.9.4", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-modify": "^3.0.0", "rollup-plugin-postcss-lit": "^2.1.0", "storybook": "^7.6.7", "storybook-addon-mock": "^4.3.0", - "ts-lit-plugin": "^2.0.1", + "ts-lit-plugin": "^2.0.2", "tslib": "^2.6.2", "turnstile-types": "^1.2.0", "typescript": "^5.3.3", diff --git a/web/src/admin/AdminInterface/AdminInterface.ts b/web/src/admin/AdminInterface/AdminInterface.ts index 834c98f37..0f1a59ccd 100644 --- a/web/src/admin/AdminInterface/AdminInterface.ts +++ b/web/src/admin/AdminInterface/AdminInterface.ts @@ -7,7 +7,7 @@ import { import { configureSentry } from "@goauthentik/common/sentry"; import { me } from "@goauthentik/common/users"; import { WebsocketClient } from "@goauthentik/common/ws"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; import "@goauthentik/elements/messages/MessageContainer"; diff --git a/web/src/admin/AdminInterface/AdminSidebar.ts b/web/src/admin/AdminInterface/AdminSidebar.ts index 57381a1de..eb860dd5b 100644 --- a/web/src/admin/AdminInterface/AdminSidebar.ts +++ b/web/src/admin/AdminInterface/AdminSidebar.ts @@ -1,23 +1,25 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants"; import { me } from "@goauthentik/common/users"; -import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; import { AKElement } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle"; import { spread } from "@open-wc/lit-helpers"; -import { consume } from "@lit-labs/context"; import { msg, str } from "@lit/localize"; import { TemplateResult, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { map } from "lit/directives/map.js"; -import { AdminApi, CapabilitiesEnum, CoreApi, UiThemeEnum, Version } from "@goauthentik/api"; -import type { Config, SessionUser, UserSelf } from "@goauthentik/api"; +import { AdminApi, CoreApi, UiThemeEnum, Version } from "@goauthentik/api"; +import type { SessionUser, UserSelf } from "@goauthentik/api"; @customElement("ak-admin-sidebar") -export class AkAdminSidebar extends AKElement { +export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) { @property({ type: Boolean, reflect: true }) open = true; @@ -27,9 +29,6 @@ export class AkAdminSidebar extends AKElement { @state() impersonation: UserSelf["username"] | null = null; - @consume({ context: authentikConfigContext }) - public config!: Config; - constructor() { super(); new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => { @@ -201,7 +200,7 @@ export class AkAdminSidebar extends AKElement { } renderEnterpriseMessage() { - return this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise) + return this.can(CapabilitiesEnum.IsEnterprise) ? html` ${msg("Enterprise")} diff --git a/web/src/admin/admin-overview/AdminOverviewPage.ts b/web/src/admin/admin-overview/AdminOverviewPage.ts index a7e210a7c..9b79f5334 100644 --- a/web/src/admin/admin-overview/AdminOverviewPage.ts +++ b/web/src/admin/admin-overview/AdminOverviewPage.ts @@ -74,10 +74,7 @@ export class AdminOverviewPage extends AKElement { } render(): TemplateResult { - let name = this.user?.user.username; - if (this.user?.user.name) { - name = this.user.user.name; - } + const name = this.user?.user.name ?? this.user?.user.username; return html` ${msg(str`Welcome, ${name}.`)} diff --git a/web/src/admin/applications/ApplicationForm.ts b/web/src/admin/applications/ApplicationForm.ts index 970b3638e..ead17e9b2 100644 --- a/web/src/admin/applications/ApplicationForm.ts +++ b/web/src/admin/applications/ApplicationForm.ts @@ -1,13 +1,16 @@ import "@goauthentik/admin/applications/ProviderSelectModal"; import { iconHelperText } from "@goauthentik/admin/helperText"; -import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-file-input"; import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-textarea-input"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/ModalForm"; @@ -22,13 +25,7 @@ import { TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { - Application, - CapabilitiesEnum, - CoreApi, - PolicyEngineMode, - Provider, -} from "@goauthentik/api"; +import { Application, CoreApi, PolicyEngineMode, Provider } from "@goauthentik/api"; import "./components/ak-backchannel-input"; import "./components/ak-provider-search-input"; @@ -48,7 +45,7 @@ export const policyOptions = [ ]; @customElement("ak-application-form") -export class ApplicationForm extends ModelForm { +export class ApplicationForm extends WithCapabilitiesConfig(ModelForm) { constructor() { super(); this.handleConfirmBackchannelProviders = this.handleConfirmBackchannelProviders.bind(this); @@ -93,8 +90,7 @@ export class ApplicationForm extends ModelForm { applicationRequest: data, }); } - const c = await config(); - if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) { + if (this.can(CapabilitiesEnum.CanSaveMedia)) { const icon = this.getFormFiles()["metaIcon"]; if (icon || this.clearIcon) { await new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({ @@ -140,21 +136,21 @@ export class ApplicationForm extends ModelForm { return html`
{ @@ -209,11 +205,11 @@ export class ApplicationForm extends ModelForm { )} > - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.metaIcon diff --git a/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts index f95972e0a..c4a0b6c06 100644 --- a/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts +++ b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts @@ -7,7 +7,7 @@ import "@goauthentik/components/ak-number-input"; import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -32,7 +32,7 @@ import { } from "./LDAPOptionsAndHelp"; @customElement("ak-application-wizard-authentication-by-ldap") -export class ApplicationWizardApplicationDetails extends BaseProviderPanel { +export class ApplicationWizardApplicationDetails extends WithBrandConfig(BaseProviderPanel) { render() { const provider = this.wizard.provider as LDAPProvider | undefined; const errors = this.wizard.errors.provider; @@ -57,7 +57,7 @@ export class ApplicationWizardApplicationDetails extends BaseProviderPanel {

diff --git a/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts index 836ec721f..854e2570b 100644 --- a/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts +++ b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts @@ -3,7 +3,7 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-text-input"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -17,7 +17,7 @@ import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/ import BaseProviderPanel from "../BaseProviderPanel"; @customElement("ak-application-wizard-authentication-by-radius") -export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel { +export class ApplicationWizardAuthenticationByRadius extends WithBrandConfig(BaseProviderPanel) { render() { const provider = this.wizard.provider as RadiusProvider | undefined; const errors = this.wizard.errors.provider; @@ -42,7 +42,7 @@ export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel {

diff --git a/web/src/admin/flows/FlowForm.ts b/web/src/admin/flows/FlowForm.ts index 1d279070f..3925f6db9 100644 --- a/web/src/admin/flows/FlowForm.ts +++ b/web/src/admin/flows/FlowForm.ts @@ -1,8 +1,11 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/utils"; import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum"; -import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; @@ -14,7 +17,6 @@ import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { - CapabilitiesEnum, DeniedActionEnum, Flow, FlowDesignationEnum, @@ -24,7 +26,7 @@ import { } from "@goauthentik/api"; @customElement("ak-flow-form") -export class FlowForm extends ModelForm { +export class FlowForm extends WithCapabilitiesConfig(ModelForm) { async loadInstance(pk: string): Promise { const flow = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({ slug: pk, @@ -54,8 +56,8 @@ export class FlowForm extends ModelForm { flowRequest: data, }); } - const c = await config(); - if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) { + + if (this.can(CapabilitiesEnum.CanSaveMedia)) { const icon = this.getFormFiles()["background"]; if (icon || this.clearBackground) { await new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundCreate({ @@ -340,7 +342,7 @@ export class FlowForm extends ModelForm { - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` diff --git a/web/src/admin/groups/RelatedUserList.ts b/web/src/admin/groups/RelatedUserList.ts index c5230749f..91feb6cd4 100644 --- a/web/src/admin/groups/RelatedUserList.ts +++ b/web/src/admin/groups/RelatedUserList.ts @@ -9,7 +9,11 @@ import { MessageLevel } from "@goauthentik/common/messages"; import { uiConfig } from "@goauthentik/common/ui/config"; import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/forms/DeleteBulkForm"; @@ -33,7 +37,6 @@ import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import { - CapabilitiesEnum, CoreApi, CoreUsersListTypeEnum, Group, @@ -107,7 +110,7 @@ export class RelatedUserAdd extends Form<{ users: number[] }> { } @customElement("ak-user-related-list") -export class RelatedUserList extends Table { +export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Table)) { expandable = true; checkbox = true; @@ -188,8 +191,7 @@ export class RelatedUserList extends Table { row(item: User): TemplateResult[] { const canImpersonate = - rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && - item.pk !== this.me?.user.pk; + this.can(CapabilitiesEnum.CanImpersonate) && item.pk !== this.me?.user.pk; return [ html`

@@ -293,7 +295,7 @@ export class RelatedUserList extends Table { ${msg("Set password")} - ${rootInterface()?.brand?.flowRecovery + ${this.brand?.flowRecovery ? html` { ${msg("Hold control/command to select multiple items.")}

- - -

- ${msg("Set custom attributes using YAML or JSON.")} -

-

- ${msg("See more here:")}  - ${msg("Documentation")} -

- `; + + ${msg("Advanced settings")} + + +

+ ${msg("Set custom attributes using YAML or JSON.")} +

+

+ ${msg("See more here:")}  + ${msg("Documentation")} +

+
+
`; } } diff --git a/web/src/admin/property-mappings/PropertyMappingWizard.ts b/web/src/admin/property-mappings/PropertyMappingWizard.ts index 4773dd93a..4f0ab6122 100644 --- a/web/src/admin/property-mappings/PropertyMappingWizard.ts +++ b/web/src/admin/property-mappings/PropertyMappingWizard.ts @@ -13,21 +13,24 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; +import { CSSResult, TemplateResult, html, nothing } from "lit"; +import { property, state } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { PropertymappingsApi, TypeCreate } from "@goauthentik/api"; +import { EnterpriseApi, LicenseSummary, PropertymappingsApi, TypeCreate } from "@goauthentik/api"; @customElement("ak-property-mapping-wizard-initial") export class InitialPropertyMappingWizardPage extends WizardPage { @property({ attribute: false }) mappingTypes: TypeCreate[] = []; + @property({ attribute: false }) + enterprise?: LicenseSummary; + static get styles(): CSSResult[] { return [PFBase, PFForm, PFButton, PFRadio]; } @@ -60,11 +63,20 @@ export class InitialPropertyMappingWizardPage extends WizardPage { ]; this.host.isValid = true; }} + ?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false} /> ${type.description} + ${type.requiresEnterprise && !this.enterprise?.hasLicense + ? html` + + ${msg("Provider require enterprise.")} + ${msg("Learn more")} + + ` + : nothing} `; })} `; @@ -80,10 +92,16 @@ export class PropertyMappingWizard extends AKElement { @property({ attribute: false }) mappingTypes: TypeCreate[] = []; - firstUpdated(): void { - new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypesList().then((types) => { - this.mappingTypes = types; - }); + @state() + enterprise?: LicenseSummary; + + async firstUpdated(): Promise { + this.mappingTypes = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsAllTypesList(); + this.enterprise = await new EnterpriseApi( + DEFAULT_CONFIG, + ).enterpriseLicenseSummaryRetrieve(); } render(): TemplateResult { diff --git a/web/src/admin/providers/ProviderWizard.ts b/web/src/admin/providers/ProviderWizard.ts index a65945354..7f19b4d02 100644 --- a/web/src/admin/providers/ProviderWizard.ts +++ b/web/src/admin/providers/ProviderWizard.ts @@ -4,6 +4,7 @@ import "@goauthentik/admin/providers/proxy/ProxyProviderForm"; import "@goauthentik/admin/providers/saml/SAMLProviderForm"; import "@goauthentik/admin/providers/saml/SAMLProviderImportForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/elements/Alert"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ProxyForm"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; @@ -13,8 +14,8 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; +import { CSSResult, TemplateResult, html, nothing } from "lit"; +import { property, state } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; @@ -22,13 +23,16 @@ import PFHint from "@patternfly/patternfly/components/Hint/hint.css"; import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { ProvidersApi, TypeCreate } from "@goauthentik/api"; +import { EnterpriseApi, LicenseSummary, ProvidersApi, TypeCreate } from "@goauthentik/api"; @customElement("ak-provider-wizard-initial") export class InitialProviderWizardPage extends WizardPage { @property({ attribute: false }) providerTypes: TypeCreate[] = []; + @property({ attribute: false }) + enterprise?: LicenseSummary; + static get styles(): CSSResult[] { return [PFBase, PFForm, PFHint, PFButton, PFRadio]; } @@ -79,9 +83,18 @@ export class InitialProviderWizardPage extends WizardPage { this.host.steps = ["initial", `type-${type.component}`]; this.host.isValid = true; }} + ?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false} /> ${type.description} + ${type.requiresEnterprise && !this.enterprise?.hasLicense + ? html` + + ${msg("Provider require enterprise.")} + ${msg("Learn more")} + + ` + : nothing} `; })} `; @@ -100,15 +113,19 @@ export class ProviderWizard extends AKElement { @property({ attribute: false }) providerTypes: TypeCreate[] = []; + @state() + enterprise?: LicenseSummary; + @property({ attribute: false }) finalHandler: () => Promise = () => { return Promise.resolve(); }; - firstUpdated(): void { - new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => { - this.providerTypes = types; - }); + async firstUpdated(): Promise { + this.providerTypes = await new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList(); + this.enterprise = await new EnterpriseApi( + DEFAULT_CONFIG, + ).enterpriseLicenseSummaryRetrieve(); } render(): TemplateResult { @@ -121,7 +138,11 @@ export class ProviderWizard extends AKElement { return this.finalHandler(); }} > - + ${this.providerTypes.map((type) => { return html` diff --git a/web/src/admin/providers/ldap/LDAPProviderForm.ts b/web/src/admin/providers/ldap/LDAPProviderForm.ts index d64109b3c..db426cc1a 100644 --- a/web/src/admin/providers/ldap/LDAPProviderForm.ts +++ b/web/src/admin/providers/ldap/LDAPProviderForm.ts @@ -3,7 +3,7 @@ import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/Radio"; @@ -25,7 +25,7 @@ import { } from "@goauthentik/api"; @customElement("ak-provider-ldap-form") -export class LDAPProviderFormPage extends BaseProviderForm { +export class LDAPProviderFormPage extends WithBrandConfig(BaseProviderForm) { async loadInstance(pk: number): Promise { return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ id: pk, @@ -68,7 +68,7 @@ export class LDAPProviderFormPage extends BaseProviderForm {

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

diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts index 000df8cad..74f3acbeb 100644 --- a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts +++ b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts @@ -290,9 +290,13 @@ export class OAuth2ProviderFormPage extends BaseProviderForm { let selected = false; if (!provider?.propertyMappings) { selected = - scope.managed?.startsWith( + // By default select all managed scope mappings, except offline_access + (scope.managed?.startsWith( "goauthentik.io/providers/oauth2/scope-", - ) || false; + ) && + scope.managed !== + "goauthentik.io/providers/oauth2/scope-offline_access") || + false; } else { selected = Array.from(provider?.propertyMappings).some((su) => { return su == scope.pk; diff --git a/web/src/admin/providers/rac/EndpointForm.ts b/web/src/admin/providers/rac/EndpointForm.ts index af83af23f..0f23f4fca 100644 --- a/web/src/admin/providers/rac/EndpointForm.ts +++ b/web/src/admin/providers/rac/EndpointForm.ts @@ -106,6 +106,23 @@ export class EndpointForm extends ModelForm { />

${msg("Hostname/IP to connect to.")}

+ + +

+ ${msg( + "Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.", + )} +

+
{ +export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm) { loadInstance(pk: number): Promise { return new ProvidersApi(DEFAULT_CONFIG).providersRadiusRetrieve({ id: pk, @@ -57,7 +57,7 @@ export class RadiusProviderFormPage extends BaseProviderForm {

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

diff --git a/web/src/admin/sources/oauth/OAuthSourceForm.ts b/web/src/admin/sources/oauth/OAuthSourceForm.ts index cf0209fcb..92763bf7b 100644 --- a/web/src/admin/sources/oauth/OAuthSourceForm.ts +++ b/web/src/admin/sources/oauth/OAuthSourceForm.ts @@ -4,9 +4,12 @@ import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -17,7 +20,6 @@ import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { - CapabilitiesEnum, FlowsInstancesListDesignationEnum, OAuthSource, OAuthSourceRequest, @@ -28,7 +30,7 @@ import { } from "@goauthentik/api"; @customElement("ak-source-oauth-form") -export class OAuthSourceForm extends BaseSourceForm { +export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm) { async loadInstance(pk: string): Promise { const source = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthRetrieve({ slug: pk, @@ -318,7 +320,7 @@ export class OAuthSourceForm extends BaseSourceForm { />

${placeholderHelperText}

- ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.icon diff --git a/web/src/admin/sources/plex/PlexSourceForm.ts b/web/src/admin/sources/plex/PlexSourceForm.ts index 8091067ff..8444e1112 100644 --- a/web/src/admin/sources/plex/PlexSourceForm.ts +++ b/web/src/admin/sources/plex/PlexSourceForm.ts @@ -2,10 +2,13 @@ import "@goauthentik/admin/common/ak-flow-search/ak-source-flow-search"; import { iconHelperText, placeholderHelperText } from "@goauthentik/admin/helperText"; import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; -import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -16,7 +19,6 @@ import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { - CapabilitiesEnum, FlowsInstancesListDesignationEnum, PlexSource, SourcesApi, @@ -24,7 +26,7 @@ import { } from "@goauthentik/api"; @customElement("ak-source-plex-form") -export class PlexSourceForm extends BaseSourceForm { +export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm) { async loadInstance(pk: string): Promise { const source = await new SourcesApi(DEFAULT_CONFIG).sourcesPlexRetrieve({ slug: pk, @@ -63,8 +65,7 @@ export class PlexSourceForm extends BaseSourceForm { plexSourceRequest: data, }); } - const c = await config(); - if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) { + if (this.can(CapabilitiesEnum.CanSaveMedia)) { const icon = this.getFormFiles()["icon"]; if (icon || this.clearIcon) { await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({ @@ -255,7 +256,7 @@ export class PlexSourceForm extends BaseSourceForm { />

${placeholderHelperText}

- ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.icon diff --git a/web/src/admin/sources/saml/SAMLSourceForm.ts b/web/src/admin/sources/saml/SAMLSourceForm.ts index 76e996322..c969411fb 100644 --- a/web/src/admin/sources/saml/SAMLSourceForm.ts +++ b/web/src/admin/sources/saml/SAMLSourceForm.ts @@ -5,7 +5,10 @@ import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/Radio"; @@ -18,7 +21,6 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { BindingTypeEnum, - CapabilitiesEnum, DigestAlgorithmEnum, FlowsInstancesListDesignationEnum, NameIdPolicyEnum, @@ -29,7 +31,7 @@ import { } from "@goauthentik/api"; @customElement("ak-source-saml-form") -export class SAMLSourceForm extends BaseSourceForm { +export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm) { @state() clearIcon = false; @@ -149,7 +151,7 @@ export class SAMLSourceForm extends BaseSourceForm { - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.icon diff --git a/web/src/admin/users/UserListPage.ts b/web/src/admin/users/UserListPage.ts index 2fcb6bbb1..4f6895560 100644 --- a/web/src/admin/users/UserListPage.ts +++ b/web/src/admin/users/UserListPage.ts @@ -12,6 +12,11 @@ import { DefaultUIConfig, uiConfig } from "@goauthentik/common/ui/config"; import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import { PFSize } from "@goauthentik/elements/Spinner"; import "@goauthentik/elements/TreeView"; import "@goauthentik/elements/buttons/ActionButton"; @@ -33,14 +38,7 @@ import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; -import { - CapabilitiesEnum, - CoreApi, - ResponseError, - SessionUser, - User, - UserPath, -} from "@goauthentik/api"; +import { CoreApi, ResponseError, SessionUser, User, UserPath } from "@goauthentik/api"; export const requestRecoveryLink = (user: User) => new CoreApi(DEFAULT_CONFIG) @@ -93,7 +91,7 @@ const recoveryButtonStyles = css` `; @customElement("ak-user-list") -export class UserListPage extends TablePage { +export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePage)) { expandable = true; checkbox = true; @@ -244,8 +242,7 @@ export class UserListPage extends TablePage { row(item: User): TemplateResult[] { const canImpersonate = - rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && - item.pk !== this.me?.user.pk; + this.can(CapabilitiesEnum.CanImpersonate) && item.pk !== this.me?.user.pk; return [ html`
${item.username}
@@ -355,7 +352,7 @@ export class UserListPage extends TablePage { ${msg("Set password")} - ${rootInterface()?.brand?.flowRecovery + ${this.brand.flowRecovery ? html` { @@ -163,8 +164,7 @@ export class UserViewPage extends AKElement { renderActionButtons(user: User) { const canImpersonate = - rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && - user.pk !== this.me?.user.pk; + this.can(CapabilitiesEnum.CanImpersonate) && user.pk !== this.me?.user.pk; return html`
diff --git a/web/src/elements/AuthentikContexts.ts b/web/src/elements/AuthentikContexts.ts index 97a89a881..3a0f1dd1b 100644 --- a/web/src/elements/AuthentikContexts.ts +++ b/web/src/elements/AuthentikContexts.ts @@ -1,7 +1,9 @@ import { createContext } from "@lit-labs/context"; -import { type Config } from "@goauthentik/api"; +import type { Config, CurrentBrand } from "@goauthentik/api"; export const authentikConfigContext = createContext(Symbol("authentik-config-context")); +export const authentikBrandContext = createContext(Symbol("authentik-brand-context")); + export default authentikConfigContext; diff --git a/web/src/elements/Base.ts b/web/src/elements/Base.ts index 1817ef331..3e251ce12 100644 --- a/web/src/elements/Base.ts +++ b/web/src/elements/Base.ts @@ -1,20 +1,18 @@ -import { brand, config } from "@goauthentik/common/api/config"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; -import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; +import { UIConfig } from "@goauthentik/common/ui/config"; import { adaptCSS } from "@goauthentik/common/utils"; -import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; +import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; -import { ContextProvider } from "@lit-labs/context"; import { localized } from "@lit/localize"; -import { CSSResult, LitElement } from "lit"; -import { state } from "lit/decorators.js"; +import { LitElement } from "lit"; import AKGlobal from "@goauthentik/common/styles/authentik.css"; import ThemeDark from "@goauthentik/common/styles/theme-dark.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api"; +import { AdoptedStyleSheetsElement } from "./types"; + type AkInterface = HTMLElement & { getTheme: () => Promise; brand?: CurrentBrand; @@ -25,13 +23,6 @@ type AkInterface = HTMLElement & { export const rootInterface = (): T | undefined => (document.body.querySelector("[data-ak-interface-root]") as T) ?? undefined; -export function ensureCSSStyleSheet(css: CSSStyleSheet | CSSResult): CSSStyleSheet { - if (css instanceof CSSResult) { - return css.styleSheet!; - } - return css; -} - let css: Promise | undefined; function fetchCustomCSS(): Promise { if (!css) { @@ -52,10 +43,6 @@ function fetchCustomCSS(): Promise { return css; } -export interface AdoptedStyleSheetsElement { - adoptedStyleSheets: readonly CSSStyleSheet[]; -} - const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)"; @localized() @@ -175,49 +162,3 @@ export class AKElement extends LitElement { this.requestUpdate(); } } - -export class Interface extends AKElement implements AkInterface { - @state() - brand?: CurrentBrand; - - @state() - uiConfig?: UIConfig; - - _configContext = new ContextProvider(this, { - context: authentikConfigContext, - initialValue: undefined, - }); - - _config?: Config; - - @state() - set config(c: Config) { - this._config = c; - this._configContext.setValue(c); - this.requestUpdate(); - } - - get config(): Config | undefined { - return this._config; - } - - constructor() { - super(); - document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)]; - brand().then((brand) => (this.brand = brand)); - config().then((config) => (this.config = config)); - this.dataset.akInterfaceRoot = "true"; - } - - _activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum): void { - super._activateTheme(root, theme); - super._activateTheme(document, theme); - } - - async getTheme(): Promise { - if (!this.uiConfig) { - this.uiConfig = await uiConfig(); - } - return this.uiConfig.theme?.base || UiThemeEnum.Automatic; - } -} diff --git a/web/src/elements/Interface/Interface.ts b/web/src/elements/Interface/Interface.ts new file mode 100644 index 000000000..ed4e57c9a --- /dev/null +++ b/web/src/elements/Interface/Interface.ts @@ -0,0 +1,85 @@ +import { brand, config } from "@goauthentik/common/api/config"; +import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; +import { + authentikBrandContext, + authentikConfigContext, +} from "@goauthentik/elements/AuthentikContexts"; +import type { AdoptedStyleSheetsElement } from "@goauthentik/elements/types"; +import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; + +import { ContextProvider } from "@lit-labs/context"; +import { state } from "lit/decorators.js"; + +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api"; + +import { AKElement } from "../Base"; + +type AkInterface = HTMLElement & { + getTheme: () => Promise; + brand?: CurrentBrand; + uiConfig?: UIConfig; + config?: Config; +}; + +export class Interface extends AKElement implements AkInterface { + @state() + uiConfig?: UIConfig; + + _configContext = new ContextProvider(this, { + context: authentikConfigContext, + initialValue: undefined, + }); + + _config?: Config; + + @state() + set config(c: Config) { + this._config = c; + this._configContext.setValue(c); + this.requestUpdate(); + } + + get config(): Config | undefined { + return this._config; + } + + _brandContext = new ContextProvider(this, { + context: authentikBrandContext, + initialValue: undefined, + }); + + _brand?: CurrentBrand; + + @state() + set brand(c: CurrentBrand) { + this._brand = c; + this._brandContext.setValue(c); + this.requestUpdate(); + } + + get brand(): CurrentBrand | undefined { + return this._brand; + } + + constructor() { + super(); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)]; + brand().then((brand) => (this.brand = brand)); + config().then((config) => (this.config = config)); + this.dataset.akInterfaceRoot = "true"; + } + + _activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum): void { + super._activateTheme(root, theme); + super._activateTheme(document, theme); + } + + async getTheme(): Promise { + if (!this.uiConfig) { + this.uiConfig = await uiConfig(); + } + return this.uiConfig.theme?.base || UiThemeEnum.Automatic; + } +} diff --git a/web/src/elements/Interface/authentikConfigProvider.ts b/web/src/elements/Interface/authentikConfigProvider.ts new file mode 100644 index 000000000..5b2027fd0 --- /dev/null +++ b/web/src/elements/Interface/authentikConfigProvider.ts @@ -0,0 +1,20 @@ +import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; + +import { consume } from "@lit-labs/context"; +import type { LitElement } from "lit"; + +import type { Config } from "@goauthentik/api"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Constructor = new (...args: any[]) => T; + +export function WithAuthentikConfig>( + superclass: T, + subscribe = true, +) { + abstract class WithAkConfigProvider extends superclass { + @consume({ context: authentikConfigContext, subscribe }) + public authentikConfig!: Config; + } + return WithAkConfigProvider; +} diff --git a/web/src/elements/Interface/brandProvider.ts b/web/src/elements/Interface/brandProvider.ts new file mode 100644 index 000000000..242764bf7 --- /dev/null +++ b/web/src/elements/Interface/brandProvider.ts @@ -0,0 +1,20 @@ +import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts"; + +import { consume } from "@lit-labs/context"; +import type { LitElement } from "lit"; + +import type { CurrentBrand } from "@goauthentik/api"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Constructor = abstract new (...args: any[]) => T; + +export function WithBrandConfig>( + superclass: T, + subscribe = true, +) { + abstract class WithBrandProvider extends superclass { + @consume({ context: authentikBrandContext, subscribe }) + public brand!: CurrentBrand; + } + return WithBrandProvider; +} diff --git a/web/src/elements/Interface/capabilitiesProvider.ts b/web/src/elements/Interface/capabilitiesProvider.ts new file mode 100644 index 000000000..402653880 --- /dev/null +++ b/web/src/elements/Interface/capabilitiesProvider.ts @@ -0,0 +1,69 @@ +import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; + +import { consume } from "@lit-labs/context"; +import type { LitElement } from "lit"; + +import { CapabilitiesEnum } from "@goauthentik/api"; +import { Config } from "@goauthentik/api"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Constructor = abstract new (...args: any[]) => T; + +// Using a unique, lexically scoped, and locally static symbol as the field name for the context +// means that it's inaccessible to any child class looking for it. It's one of the strongest privacy +// guarantees in JavaScript. + +class WCC { + public static readonly capabilitiesConfig: unique symbol = Symbol(); +} + +/** + * withCapabilitiesContext mixes in a single method to any LitElement, `can()`, which takes a + * CapabilitiesEnum and returns true or false. + * + * Usage: + * + * After importing, simply mixin this function: + * + * ``` + * export class AkMyNiftyNewFeature extends withCapabilitiesContext(AKElement) { + * ``` + * + * And then if you need to check on a capability: + * + * ``` + * if (this.can(CapabilitiesEnum.IsEnterprise) { ... } + * ``` + * + * This code re-exports CapabilitiesEnum, so you won't have to import it on a separate line if you + * don't need anything else from the API. + * + * Passing `true` as the second mixin argument will cause the inheriting class to subscribe to the + * configuration context. Should the context be explicitly reset, all active web components that are + * currently active and subscribed to the context will automatically have a `requestUpdate()` + * triggered with the new configuration. + * + */ + +export function WithCapabilitiesConfig>( + superclass: T, + subscribe = true, +) { + abstract class CapabilitiesContext extends superclass { + @consume({ context: authentikConfigContext, subscribe }) + private [WCC.capabilitiesConfig]!: Config; + + can(c: CapabilitiesEnum) { + if (!this[WCC.capabilitiesConfig]) { + throw new Error( + "ConfigContext: Attempted to access site configuration before initialization.", + ); + } + return this[WCC.capabilitiesConfig].capabilities.includes(c); + } + } + + return CapabilitiesContext; +} + +export { CapabilitiesEnum }; diff --git a/web/src/elements/Interface/index.ts b/web/src/elements/Interface/index.ts new file mode 100644 index 000000000..e7d946cf6 --- /dev/null +++ b/web/src/elements/Interface/index.ts @@ -0,0 +1,4 @@ +import { Interface } from "./Interface"; + +export { Interface }; +export default Interface; diff --git a/web/src/elements/PageHeader.ts b/web/src/elements/PageHeader.ts index 83a783b1d..d7d7df9f0 100644 --- a/web/src/elements/PageHeader.ts +++ b/web/src/elements/PageHeader.ts @@ -8,7 +8,8 @@ import { } from "@goauthentik/common/constants"; import { currentInterface } from "@goauthentik/common/sentry"; import { me } from "@goauthentik/common/users"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; @@ -23,7 +24,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { EventsApi } from "@goauthentik/api"; @customElement("ak-page-header") -export class PageHeader extends AKElement { +export class PageHeader extends WithBrandConfig(AKElement) { @property() icon?: string; @@ -35,9 +36,8 @@ export class PageHeader extends AKElement { @property() set header(value: string) { - const brand = rootInterface()?.brand; const currentIf = currentInterface(); - let title = brand?.brandingTitle || TITLE_DEFAULT; + let title = this.brand?.brandingTitle || TITLE_DEFAULT; if (currentIf === "admin") { title = `${msg("Admin")} - ${title}`; } diff --git a/web/src/elements/ak-locale-context/definitions.ts b/web/src/elements/ak-locale-context/definitions.ts index e920e85b1..018c9e2a1 100644 --- a/web/src/elements/ak-locale-context/definitions.ts +++ b/web/src/elements/ak-locale-context/definitions.ts @@ -46,6 +46,8 @@ const LOCALE_TABLE: LocaleRow[] = [ ["es", /^es([_-]|$)/i, () => msg("Spanish"), async () => await import("@goauthentik/locales/es")], ["de", /^de([_-]|$)/i, () => msg("German"), async () => await import("@goauthentik/locales/de")], ["fr", /^fr([_-]|$)/i, () => msg("French"), async () => await import("@goauthentik/locales/fr")], + ["ko", /^ko([_-]|$)/i, () => msg("Korean"), async () => await import("@goauthentik/locales/ko")], + ["nl", /^nl([_-]|$)/i, () => msg("Dutch"), async () => await import("@goauthentik/locales/nl")], ["pl", /^pl([_-]|$)/i, () => msg("Polish"), async () => await import("@goauthentik/locales/pl")], ["tr", /^tr([_-]|$)/i, () => msg("Turkish"), async () => await import("@goauthentik/locales/tr")], ["zh-Hant", /^zh[_-](HK|Hant)/i, () => msg("Chinese (traditional)"), async () => await import("@goauthentik/locales/zh-Hant")], diff --git a/web/src/elements/enterprise/EnterpriseStatusBanner.ts b/web/src/elements/enterprise/EnterpriseStatusBanner.ts index 0ac115457..09d376759 100644 --- a/web/src/elements/enterprise/EnterpriseStatusBanner.ts +++ b/web/src/elements/enterprise/EnterpriseStatusBanner.ts @@ -21,10 +21,8 @@ export class EnterpriseStatusBanner extends AKElement { return [PFBanner]; } - firstUpdated(): void { - new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((b) => { - this.summary = b; - }); + async firstUpdated(): Promise { + this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve(); } renderBanner(): TemplateResult { diff --git a/web/src/elements/sidebar/SidebarBrand.ts b/web/src/elements/sidebar/SidebarBrand.ts index daf2c8ba5..a231cb78a 100644 --- a/web/src/elements/sidebar/SidebarBrand.ts +++ b/web/src/elements/sidebar/SidebarBrand.ts @@ -1,6 +1,6 @@ import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants"; -import { first } from "@goauthentik/common/utils"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement } from "lit/decorators.js"; @@ -27,7 +27,7 @@ export const DefaultBrand: CurrentBrand = { }; @customElement("ak-sidebar-brand") -export class SidebarBrand extends AKElement { +export class SidebarBrand extends WithBrandConfig(AKElement) { static get styles(): CSSResult[] { return [ PFBase, @@ -85,10 +85,7 @@ export class SidebarBrand extends AKElement {
authentik Logo diff --git a/web/src/elements/types.ts b/web/src/elements/types.ts new file mode 100644 index 000000000..4273ab6f9 --- /dev/null +++ b/web/src/elements/types.ts @@ -0,0 +1,3 @@ +export interface AdoptedStyleSheetsElement { + adoptedStyleSheets: readonly CSSStyleSheet[]; +} diff --git a/web/src/elements/utils/ensureCSSStyleSheet.ts b/web/src/elements/utils/ensureCSSStyleSheet.ts new file mode 100644 index 000000000..26f2ff898 --- /dev/null +++ b/web/src/elements/utils/ensureCSSStyleSheet.ts @@ -0,0 +1,4 @@ +import { CSSResult } from "lit"; + +export const ensureCSSStyleSheet = (css: CSSStyleSheet | CSSResult): CSSStyleSheet => + css instanceof CSSResult ? css.styleSheet! : css; diff --git a/web/src/enterprise/rac/index.ts b/web/src/enterprise/rac/index.ts index 27a8623e0..9761462e4 100644 --- a/web/src/enterprise/rac/index.ts +++ b/web/src/enterprise/rac/index.ts @@ -1,5 +1,5 @@ import { TITLE_DEFAULT } from "@goauthentik/app/common/constants"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/LoadingOverlay"; import Guacamole from "guacamole-common-js"; diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index 3d3f1940c..6c444a0a1 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -8,7 +8,7 @@ import { globalAK } from "@goauthentik/common/global"; import { configureSentry } from "@goauthentik/common/sentry"; import { first } from "@goauthentik/common/utils"; import { WebsocketClient } from "@goauthentik/common/ws"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/LoadingOverlay"; import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/flow/sources/apple/AppleLoginInit"; diff --git a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts index 8331938db..aecd93087 100644 --- a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts +++ b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts @@ -89,6 +89,9 @@ export class AuthenticatorValidateStage display: flex; align-items: center; } + :host([theme="dark"]) .authenticator-button { + color: var(--ak-dark-foreground) !important; + } i { font-size: 1.5rem; padding: 1rem 0; diff --git a/web/src/flow/stages/prompt/PromptStage.ts b/web/src/flow/stages/prompt/PromptStage.ts index 877d02119..09cc6959e 100644 --- a/web/src/flow/stages/prompt/PromptStage.ts +++ b/web/src/flow/stages/prompt/PromptStage.ts @@ -1,6 +1,9 @@ -import { rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Divider"; import "@goauthentik/elements/EmptyState"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import { LOCALES } from "@goauthentik/elements/ak-locale-context/definitions"; import "@goauthentik/elements/forms/FormElement"; import { BaseStage } from "@goauthentik/flow/stages/base"; @@ -20,7 +23,6 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { - CapabilitiesEnum, PromptChallenge, PromptChallengeResponseRequest, PromptTypeEnum, @@ -28,7 +30,9 @@ import { } from "@goauthentik/api"; @customElement("ak-stage-prompt") -export class PromptStage extends BaseStage { +export class PromptStage extends WithCapabilitiesConfig( + BaseStage, +) { static get styles(): CSSResult[] { return [ PFBase, @@ -193,10 +197,7 @@ ${prompt.initialValue} `; })}`; case PromptTypeEnum.AkLocale: { - const inDebug = rootInterface()?.config?.capabilities.includes( - CapabilitiesEnum.CanDebug, - ); - const locales = inDebug + const locales = this.can(CapabilitiesEnum.CanDebug) ? LOCALES : LOCALES.filter((locale) => locale.code !== "debug"); const options = locales.map( diff --git a/web/src/locale-codes.ts b/web/src/locale-codes.ts index 86337dc8d..8b7a36ac1 100644 --- a/web/src/locale-codes.ts +++ b/web/src/locale-codes.ts @@ -15,10 +15,13 @@ export const targetLocales = [ `en`, `es`, `fr`, + `ko`, + `nl`, `pl`, `pseudo-LOCALE`, `tr`, `zh_TW`, + `zh-CN`, `zh-Hans`, `zh-Hant`, ] as const; @@ -32,10 +35,13 @@ export const allLocales = [ `en`, `es`, `fr`, + `ko`, + `nl`, `pl`, `pseudo-LOCALE`, `tr`, `zh_TW`, + `zh-CN`, `zh-Hans`, `zh-Hant`, ] as const; diff --git a/web/src/standalone/api-browser/index.ts b/web/src/standalone/api-browser/index.ts index b9b973c96..703caed24 100644 --- a/web/src/standalone/api-browser/index.ts +++ b/web/src/standalone/api-browser/index.ts @@ -2,7 +2,7 @@ import { CSRFHeaderName } from "@goauthentik/common/api/middleware"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { globalAK } from "@goauthentik/common/global"; import { first, getCookie } from "@goauthentik/common/utils"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/ak-locale-context"; import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; import "rapidoc"; diff --git a/web/src/standalone/loading/index.ts b/web/src/standalone/loading/index.ts index 6d22f5ed7..e1d54f323 100644 --- a/web/src/standalone/loading/index.ts +++ b/web/src/standalone/loading/index.ts @@ -1,5 +1,5 @@ import { globalAK } from "@goauthentik/common/global"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; diff --git a/web/src/stories/interface.ts b/web/src/stories/interface.ts index c4e2dc03d..1eafc6204 100644 --- a/web/src/stories/interface.ts +++ b/web/src/stories/interface.ts @@ -1,4 +1,4 @@ -import { Interface } from "@goauthentik/app/elements/Base"; +import { Interface } from "@goauthentik/app/elements/Interface"; import { customElement, property } from "lit/decorators.js"; diff --git a/web/src/user/UserInterface.ts b/web/src/user/UserInterface.ts index bef0aa2c1..be0d7ef9b 100644 --- a/web/src/user/UserInterface.ts +++ b/web/src/user/UserInterface.ts @@ -9,7 +9,7 @@ import { UserDisplay } from "@goauthentik/common/ui/config"; import { me } from "@goauthentik/common/users"; import { first } from "@goauthentik/common/utils"; import { WebsocketClient } from "@goauthentik/common/ws"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; diff --git a/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts b/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts index 6c78e1bb9..cae5ad800 100644 --- a/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts +++ b/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts @@ -2,14 +2,15 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { MessageLevel } from "@goauthentik/common/messages"; import { refreshMe } from "@goauthentik/common/users"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import { StageHost } from "@goauthentik/flow/stages/base"; import "@goauthentik/user/user-settings/details/stages/prompt/PromptStage"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -21,7 +22,6 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { ChallengeChoices, ChallengeTypes, - CurrentBrand, FlowChallengeResponseRequest, FlowErrorChallenge, FlowsApi, @@ -31,13 +31,19 @@ import { } from "@goauthentik/api"; @customElement("ak-user-settings-flow-executor") -export class UserSettingsFlowExecutor extends AKElement implements StageHost { +export class UserSettingsFlowExecutor + extends WithBrandConfig(AKElement, true) + implements StageHost +{ @property() flowSlug?: string; +<<<<<<< HEAD @state() brand?: CurrentBrand; +======= +>>>>>>> main private _challenge?: ChallengeTypes; @property({ attribute: false }) @@ -87,7 +93,6 @@ export class UserSettingsFlowExecutor extends AKElement implements StageHost { } firstUpdated(): void { - this.brand = rootInterface()?.brand; this.flowSlug = this.brand?.flowUserSettings; if (!this.flowSlug) { return; diff --git a/web/xliff/de.xlf b/web/xliff/de.xlf index 5d7e39665..07b465224 100644 --- a/web/xliff/de.xlf +++ b/web/xliff/de.xlf @@ -6192,6 +6192,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -6222,6 +6234,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/en.xlf b/web/xliff/en.xlf index 003266f4c..64b02ea4b 100644 --- a/web/xliff/en.xlf +++ b/web/xliff/en.xlf @@ -6467,6 +6467,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -6497,6 +6509,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/es.xlf b/web/xliff/es.xlf index 0c0e2ba01..3227a22fc 100644 --- a/web/xliff/es.xlf +++ b/web/xliff/es.xlf @@ -6108,6 +6108,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -6138,6 +6150,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/fr.xlf b/web/xliff/fr.xlf index 3ca62e1a1..7de6da523 100644 --- a/web/xliff/fr.xlf +++ b/web/xliff/fr.xlf @@ -8143,6 +8143,18 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti Determines how long a session lasts before being disconnected and requiring re-authorization. Détermine combien de temps une session dure avant déconnexion et ré-authorisation. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -8173,6 +8185,12 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/ko.xlf b/web/xliff/ko.xlf index ed1d838d9..2f83fbe37 100644 --- a/web/xliff/ko.xlf +++ b/web/xliff/ko.xlf @@ -1,4 +1,4 @@ - + @@ -443,11 +443,6 @@ Client IP 클라이언트 IP - - - Tenant - 테넌트 - Recent events @@ -612,8 +607,8 @@ - The URL "" was not found. - URL "" 을 찾을 수 없습니다. + The URL "" was not found. + URL "" 을 찾을 수 없습니다. @@ -1054,8 +1049,8 @@ - To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. - 리디렉션 URI를 허용하려면 이 값을 ".*"로 설정합니다. 이로 인해 발생할 수 있는 보안상의 영향에 유의하세요. + To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. + 리디렉션 URI를 허용하려면 이 값을 ".*"로 설정합니다. 이로 인해 발생할 수 있는 보안상의 영향에 유의하세요. @@ -1792,8 +1787,8 @@ - Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". - 전체 URL, 상대 경로를 입력하거나, 또는 'fa://fa-test'를 사용하여 Font Awesome 아이콘 "fa-test"를 사용합니다. + Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". + 전체 URL, 상대 경로를 입력하거나, 또는 'fa://fa-test'를 사용하여 Font Awesome 아이콘 "fa-test"를 사용합니다. @@ -2972,7 +2967,7 @@ doesn't pass when either or both of the selected options are equal or above the - Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' + Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' 그룹 구성원이 포함된 필드입니다. 'memberUid' 필드를 사용하는 경우 값에 상대적인 고유 이름이 포함된 것으로 가정합니다 (예:'memberUid=some-user' 대신 'memberUid=cn=some-user,ou=groups,...'). @@ -3617,16 +3612,6 @@ doesn't pass when either or both of the selected options are equal or above the Update Token 토큰 업데이트 - - - Successfully updated tenant. - 테넌트를 성공적으로 업데이트했습니다. - - - - Successfully created tenant. - 테넌트를 성공적으로 만들었습니다. - Domain @@ -3642,11 +3627,6 @@ doesn't pass when either or both of the selected options are equal or above the Default 기본 - - - Use this tenant for each domain that doesn't have a dedicated tenant. - 전용 테넌트가 없는 각 도메인에 이 테넌트를 사용하세요. - Branding settings @@ -3764,29 +3744,14 @@ doesn't pass when either or both of the selected options are equal or above the - When using an external logging solution for archiving, this can be set to "minutes=5". - 아카이브에 외부 로깅 솔루션을 사용하는 경우, 이 값을 "minutes=5"로 설정할 수 있습니다. + When using an external logging solution for archiving, this can be set to "minutes=5". + 아카이브에 외부 로깅 솔루션을 사용하는 경우, 이 값을 "minutes=5"로 설정할 수 있습니다. This setting only affects new Events, as the expiration is saved per-event. T만료는 이벤트별로 저장되므로 설정은 새 이벤트에만 영향을 줍니다. - - - Format: "weeks=3;days=2;hours=3,seconds=2". - 서식: "weeks=3;days=2;hours=3,seconds=2". - - - - Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this tenant. - YAML 또는 JSON을 사용하여 사용자 지정 속성을 설정합니다. 이 테넌트가 요청을 처리하는 경우 여기에서 설정한 모든 속성은 사용자가 상속받게 됩니다. - - - - Tenants - 테넌트 - Configure visual settings and defaults for different domains. @@ -3797,21 +3762,6 @@ doesn't pass when either or both of the selected options are equal or above the Default? 기본값? - - - Tenant(s) - 테넌트 - - - - Update Tenant - 테넌트 업데이트 - - - - Create Tenant - 테넌트 생성 - Policies @@ -3967,8 +3917,8 @@ doesn't pass when either or both of the selected options are equal or above the - Are you sure you want to update ""? - 정말 "" 을(를) 업데이트 하시겠습니까? + Are you sure you want to update ""? + 정말 "" 을(를) 업데이트 하시겠습니까? @@ -4081,11 +4031,6 @@ doesn't pass when either or both of the selected options are equal or above the Recovery link cannot be emailed, user has no email address saved. 복구 링크를 이메일로 보낼 수 없습니다. 사용자가 저장한 이메일 주소가 없습니다. - - - To let a user directly reset a their password, configure a recovery flow on the currently active tenant. - 사용자가 직접 비밀번호를 재설정할 수 있도록 하려면 현재 활성 상태인 테넌트에서 복구 플로우를 구성하세요. - Add User @@ -5052,8 +4997,8 @@ doesn't pass when either or both of the selected options are equal or above the - A "roaming" authenticator, like a YubiKey - YubiKey 같은 "로밍" 인증기 + A "roaming" authenticator, like a YubiKey + YubiKey 같은 "로밍" 인증기 @@ -5387,8 +5332,8 @@ doesn't pass when either or both of the selected options are equal or above the - ("", of type ) - ("", of type ) + ("", of type ) + ("", of type ) @@ -5436,7 +5381,7 @@ doesn't pass when either or both of the selected options are equal or above the - If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. + If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. 기간을 0 이상으로 설정하면, 사용자에게 '로그인 상태 유지'를 선택할 수 있는 옵션이 제공되며, 이 경우 세션이 여기에 지정된 시간만큼 연장됩니다. @@ -7813,10 +7758,6 @@ Bindings to groups/users are checked against the user of the event. A copy of this recovery link has been placed in your clipboard 이 복구 링크의 사본이 클립보드에 저장되었습니다. - - The current tenant must have a recovery flow configured to use a recovery link - 현재 테넌트에 복구 링크를 사용하도록 구성된 복구 플로우가 있어야 합니다. - Create recovery link 복구 링크 생성 @@ -7926,8 +7867,8 @@ Bindings to groups/users are checked against the user of the event. 사용자 생성과 그룹 추가에 성공했습니다. - This user will be added to the group "". - 이 사용자는 "" 그룹에 추가됩니다. + This user will be added to the group "". + 이 사용자는 "" 그룹에 추가됩니다. Pretend user exists @@ -8004,7 +7945,261 @@ Bindings to groups/users are checked against the user of the event. Require Outpost (flow can only be executed from an outpost). Outpost필요 (플로우는 Outpost에서만 실행할 수 있음). + + + Brand + + + Provider require enterprise. + + + Learn more + + + Connection expiry + + + Determines how long a session lasts before being disconnected and requiring re-authorization. + + + Connection settings. + + + Successfully updated endpoint. + + + Successfully created endpoint. + + + Protocol + + + RDP + + + SSH + + + VNC + + + Host + + + Hostname/IP to connect to. + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + + + Endpoint(s) + + + Update Endpoint + + + These bindings control which users will have access to this endpoint. Users must also have access to the application. + + + Create Endpoint + + + RAC is in preview. + + + Update RAC Provider + + + Endpoints + + + General settings + + + RDP settings + + + Ignore server certificate + + + Enable wallpaper + + + Enable font-smoothing + + + Enable full window dragging + + + Successfully updated brand. + + + Successfully created brand. + + + Use this brand for each domain that doesn't have a dedicated brand. + + + Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this brand. + + + Brands + + + Brand(s) + + + Update Brand + + + Create Brand + + + To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + + Korean + + + Dutch + + + The current brand must have a recovery flow configured to use a recovery link + + + Network binding + + + No binding + + + Bind ASN + + + Bind ASN and Network + + + Bind ASN, Network and IP + + + Configure if sessions created by this stage should be bound to the Networks they were created in. + + + GeoIP binding + + + Bind Continent + + + Bind Continent and Country + + + Bind Continent, Country and City + + + Configure if sessions created by this stage should be bound to their GeoIP-based location + + + RAC + + + Successfully updated settings. + + + Avatars + + + Configure how authentik should show avatars for users. The following values can be set: + + + Disables per-user avatars and just shows a 1x1 pixel transparent picture + + + Uses gravatar with the user's email address + + + Generated avatars based on the user's name + + + Any URL: If you want to use images hosted on another server, you can set any URL. Additionally, these placeholders can be used: + + + The user's username + + + The email address, md5 hashed + + + The user's UPN, if set (otherwise an empty string) + + + An attribute path like + attributes.something.avatar, which can be used in + combination with the file field to allow users to upload custom + avatars for themselves. + + + Multiple values can be set, comma-separated, and authentik will fallback to the next mode when no avatar could be found. + + + For example, setting this to gravatar,initials will + attempt to get an avatar from Gravatar, and if the user has not + configured on there, it will fallback to a generated avatar. + + + Default user change name + + + Enable the ability for users to change their name. + + + Default user change email + + + Enable the ability for users to change their email. + + + Default user change username + + + Enable the ability for users to change their username. + + + Footer links + + + This option configures the footer links on the flow executor pages. It must be a valid JSON list and can be used as follows: + + + GDPR compliance + + + When enabled, all the events caused by a user will be deleted upon the user's deletion. + + + Impersonation + + + Globally enable/disable impersonation. + + + System settings + + + Connection failed after attempts. + + + Re-connecting in second(s). + + + Connecting... + + + Select endpoint to connect to - \ No newline at end of file + diff --git a/web/xliff/nl.xlf b/web/xliff/nl.xlf index f2ceeb89c..224b6ded8 100644 --- a/web/xliff/nl.xlf +++ b/web/xliff/nl.xlf @@ -1,4 +1,4 @@ - + @@ -190,11 +190,6 @@ Messages Berichten - - - New version available! - Nieuwe versie beschikbaar! - Using source @@ -445,11 +440,6 @@ Client IP Client-IP - - - Tenant - Tenant - Recent events @@ -607,8 +597,8 @@ - The URL "" was not found. - De URL "" is niet gevonden. + The URL "" was not found. + De URL "" is niet gevonden. @@ -650,11 +640,6 @@ Manage users Gebruikers beheren - - - Check release notes - Controleer release-opmerkingen - Outpost status @@ -685,11 +670,6 @@ Objects created Gemaakte objecten - - - User statistics - Gebruikersstatistieken - Users created per day in the last month @@ -1057,8 +1037,8 @@ - To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. - Om elke doorverwijzings-URI toe te staan, stelt u deze waarde in op ".*". Wees u bewust van de mogelijke beveiligingsgevolgen hiervan. + To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. + Om elke doorverwijzings-URI toe te staan, stelt u deze waarde in op ".*". Wees u bewust van de mogelijke beveiligingsgevolgen hiervan. @@ -1285,11 +1265,6 @@ Validate SSL Certificates of upstream servers. Valideer SSL-certificaten van upstream-servers. - - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Gebruik deze provider met nginx's auth_request of traefik's forwardAuth. Elke toepassing/domein heeft zijn eigen provider nodig. Bovendien moet op elk domein /outpost.goauthentik.io worden gerouteerd naar de outpost (wanneer u een beheerde outpost gebruikt, wordt dit voor u gedaan). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. @@ -1798,8 +1773,8 @@ - Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". - Voer een volledige URL, een relatief pad in, of gebruik 'fa://fa-test' om het Font Awesome-pictogram "fa-test" te gebruiken. + Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". + Voer een volledige URL, een relatief pad in, of gebruik 'fa://fa-test' om het Font Awesome-pictogram "fa-test" te gebruiken. @@ -1826,11 +1801,6 @@ Slug Slug - - - Internal application name, used in URLs. - Interne naam van de applicatie, gebruikt in URL's. - Optionally enter a group name. Applications with identical groups are shown grouped together. @@ -1846,11 +1816,6 @@ Select a provider that this application should use. Selecteer een provider die door deze applicatie moet worden gebruikt. - - - Backchannel providers - Backchannel providers - Select backchannel providers which augment the functionality of the main provider. @@ -2216,11 +2181,6 @@ NameID attribute NameID-eigenschap - - - SCIM provider is in preview. - SCIM-provider is in voorbeeldweergave. - Warning: Provider is not assigned to an application as backchannel provider. @@ -2231,56 +2191,16 @@ Update SCIM Provider SCIM-provider bijwerken - - - Sync not run yet. - Synchronisatie nog niet uitgevoerd. - Run sync again Voer synchronisatie opnieuw uit - - - Application details - Applicatie details - - - - Create application - Maak applicatie aan - - - - Additional UI settings - Extra UI-instellingen - - - - OAuth2/OIDC - OAuth2/OIDC - Modern applications, APIs and Single-page applications. Moderne applicaties, API's en Single-page applicaties. - - - SAML - SAML - - - - XML-based SSO standard. Use this if your application only supports SAML. - XML-gebaseerde SSO-standaard. Gebruik dit als uw applicatie alleen SAML ondersteunt. - - - - Legacy applications which don't natively support SSO. - Verouderde applicaties die SSO niet native ondersteunen. - LDAP @@ -2291,176 +2211,16 @@ Provide an LDAP interface for applications and users to authenticate against. Geef een LDAP-interface voor applicaties en gebruikers om tegen te verifiëren. - - - Link - Koppeling - - - - Authentication method - Authenticatiemethode - - - - LDAP details - LDAP details - - - - Create service account - Serviceaccount aanmaken - - - - Create provider - Provider aanmaken - - - - Application Link - Applicatiekoppeling - - - - URL which will be opened when a user clicks on the application. - URL die wordt geopend wanneer een gebruiker op de applicatie klikt. - - - - Method details - Methode details - - - - This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically. - Deze configuratie kan worden gebruikt om programmatisch te verifiëren bij authentik met andere API's. - - - - By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password. - Standaard kunnen alle serviceaccounts zich verifiëren als deze applicatie, zolang ze een geldig token van het type app-wachtwoord hebben. - - - - Web application - Webapplicatie - - - - Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP) - Applicaties die de authenticatie serverzijde afhandelen (bijvoorbeeld Python, Go, Rust, Java, PHP) - - - - Single-page applications - Single-page applicaties - - - - Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue) - Single-page applicaties die authenticatie in de browser afhandelen (bijvoorbeeld Javascript, Angular, React, Vue) - - - - Native application - Natieve applicatie - - - - Applications which redirect users to a non-web callback (for example, Android, iOS) - Applicaties die gebruikers doorverwijzen naar een niet-web callback (bijvoorbeeld Android, iOS) - - - - API - API - - - - Authentication without user interaction, or machine-to-machine authentication. - Authenticatie zonder gebruikersinteractie of machine-tot-machine authenticatie. - - - - Application type - Applicatietype - - - - Flow used when users access this application. - Flow gebruikt wanneer gebruikers deze applicatie benaderen. - - - - Proxy details - Proxy details - - - - External domain - Extern domein - - - - External domain you will be accessing the domain from. - Extern domein waarvandaan u toegang heeft tot het domein. - - - - Import SAML Metadata - Importeer SAML Metadata - - - - Import the metadata document of the applicaation you want to configure. - Importeer het metagegevensdocument van de applicatie die je wilt configureren. - - - - Manual configuration - Handmatige configuratie - - - - Manually configure SAML - Configureer SAML handmatig - - - - SAML details - SAML details - - - - URL that authentik will redirect back to after successful authentication. - URL waarnaar authentik zal terugverwijzen na succesvolle authenticatie. - - - - Import SAML metadata - Importeer SAML metadata - New application Nieuwe applicatie - - - Create a new application. - Creëer een nieuwe applicatie. - Applications Applicaties - - - External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. - Externe applicaties die authentik gebruiken als Identity-Provider, met protocollen zoals OAuth2 en SAML. Alle applicaties worden hier getoond, zelfs degene waartoe u geen toegang heeft. - Provider Type @@ -2771,31 +2531,6 @@ If the password's score is less than or equal this value, the policy will fail. Als de score van het wachtwoord kleiner is dan of gelijk is aan deze waarde, zal het beleid falen. - - - 0: Too guessable: risky password. (guesses < 10^3) - 0: Te voorspelbaar: risicovol wachtwoord. (pogingen < 10^3) - - - - 1: Very guessable: protection from throttled online attacks. (guesses < 10^6) - 1: Zeer voorspelbaar: bescherming tegen afgeknepen online aanvallen. (pogingen < 10^6) - - - - 2: Somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8) - 2: Enigszins voorspelbaar: bescherming tegen niet-afgeknepen online aanvallen. (pogingen < 10^8) - - - - 3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10) - 3: Veilig onvoorspelbaar: matige bescherming tegen offline scenario met langzame hash. (pogingen < 10^10) - - - - 4: Very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10) - 4: Zeer onvoorspelbaar: sterke bescherming tegen offline scenario met langzame hash. (pogingen >= 10^10) - Checks the value from the policy request against several rules, mostly used to ensure password strength. @@ -3216,12 +2951,12 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d Group membership field - Veld dat leden van een groep bevat. Let op dat als het veld "memberUid" wordt gebruikt, de waarde wordt verondersteld een relatieve Distinguished Name te bevatten. Bijv. 'memberUid=some-user' in plaats van 'memberUid=cn=some-user,ou=groups,...' + Veld dat leden van een groep bevat. Let op dat als het veld "memberUid" wordt gebruikt, de waarde wordt verondersteld een relatieve Distinguished Name te bevatten. Bijv. 'memberUid=some-user' in plaats van 'memberUid=cn=some-user,ou=groups,...' - Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' - Veld dat leden van een groep bevat. Let op dat als het veld "memberUid" wordt gebruikt, de waarde wordt verondersteld een relatieve Distinguished Name te bevatten. Bijv. 'memberUid=some-user' in plaats van 'memberUid=cn=some-user,ou=groups,...' + Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' + Veld dat leden van een groep bevat. Let op dat als het veld "memberUid" wordt gebruikt, de waarde wordt verondersteld een relatieve Distinguished Name te bevatten. Bijv. 'memberUid=some-user' in plaats van 'memberUid=cn=some-user,ou=groups,...' @@ -3864,16 +3599,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d Update Token Token bijwerken - - - Successfully updated tenant. - Succesvol bijgewerkte tenant. - - - - Successfully created tenant. - Succesvol aangemaakte tenant. - Domain @@ -3889,11 +3614,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d Default Standaard - - - Use this tenant for each domain that doesn't have a dedicated tenant. - Gebruik deze tenant voor elk domein dat geen toegewijde tenant heeft. - Branding settings @@ -4011,29 +3731,14 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d - When using an external logging solution for archiving, this can be set to "minutes=5". - Als je een externe logoplossing gebruikt voor archivering, kan dit worden ingesteld op "minutes=5". + When using an external logging solution for archiving, this can be set to "minutes=5". + Als je een externe logoplossing gebruikt voor archivering, kan dit worden ingesteld op "minutes=5". This setting only affects new Events, as the expiration is saved per-event. Deze instelling heeft alleen invloed op nieuwe gebeurtenissen, omdat de vervaldatum per gebeurtenis wordt opgeslagen. - - - Format: "weeks=3;days=2;hours=3,seconds=2". - Indeling: "weeks=3;days=2;hours=3,seconds=2". - - - - Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this tenant. - Stel aangepaste eigenschappen in met behulp van YAML of JSON. Alle hier ingestelde eigenschappen worden overgenomen door gebruikers als het verzoek wordt afgehandeld door deze tenant. - - - - Tenants - Tenants - Configure visual settings and defaults for different domains. @@ -4044,21 +3749,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d Default? Standaard? - - - Tenant(s) - Tenants - - - - Update Tenant - Tenant bijwerken - - - - Create Tenant - Tenant aanmaken - Policies @@ -4214,8 +3904,8 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d - Are you sure you want to update ""? - Weet je zeker dat je "" wilt bijwerken? + Are you sure you want to update ""? + Weet je zeker dat je "" wilt bijwerken? @@ -4327,11 +4017,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d Recovery link cannot be emailed, user has no email address saved. Herstelkoppeling kan niet per e-mail worden verzonden, gebruiker heeft geen opgeslagen e-mailadres. - - - To let a user directly reset a their password, configure a recovery flow on the currently active tenant. - Om een gebruiker rechtstreeks zijn/haar wachtwoord te laten resetten, configureer een herstelflow op de momenteel actieve tenant. - Add User @@ -4537,16 +4222,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d User Info Gebruikersinformatie - - - To create a recovery link, the current tenant needs to have a recovery flow configured. - Om een herstelkoppeling te maken, moet de huidige tenant een herstelflow geconfigureerd hebben. - - - - Reset Password - Wachtwoord resetten - Actions over the last week (per 8 hours) @@ -5279,11 +4954,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d When multiple stages are selected, the user can choose which one they want to enroll. Als meerdere fases zijn geselecteerd, kan de gebruiker kiezen welke hij wil inschrijven. - - - Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello). - Fase gebruikt om een WebAutnn-authenticator te configureren (bijv. Yubikey, FaceID/Windows Hello). - User verification @@ -5294,21 +4964,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d Resident key requirement Vereiste residente sleutel - - - The authenticator should not create a dedicated credential - De authenticator mag geen toegewijde referentie maken - - - - The authenticator can create and store a dedicated credential, but if it doesn't that's alright too - De authenticator kan een toegewijde referentie maken en opslaan, maar dat hoeft niet - - - - The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur - De authenticator MOET een toegewijde referentie maken. Als dat niet lukt, is de RP voorbereid op een mogelijke fout - Authenticator Attachment @@ -5326,8 +4981,8 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d - A "roaming" authenticator, like a YubiKey - Een "roaming" authenticator, zoals een YubiKey + A "roaming" authenticator, like a YubiKey + Een "roaming" authenticator, zoals een YubiKey @@ -5412,7 +5067,7 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d Dummy stage used for testing. Shows a simple continue button and always passes. - Dummyfase voor testdoeleinden. Toont een eenvoudige knop "Doorgaan" en slaagt altijd. + Dummyfase voor testdoeleinden. Toont een eenvoudige knop "Doorgaan" en slaagt altijd. @@ -5661,8 +5316,8 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d - ("", of type ) - ("", van het type ) + ("", of type ) + ("", van het type ) @@ -5710,8 +5365,8 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d - If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. - Als ingesteld op een duur boven 0, heeft de gebruiker de mogelijkheid om te kiezen voor "ingelogd blijven", wat hun sessie zal verlengen met de hier opgegeven tijd. + If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. + Als ingesteld op een duur boven 0, heeft de gebruiker de mogelijkheid om te kiezen voor "ingelogd blijven", wat hun sessie zal verlengen met de hier opgegeven tijd. @@ -7710,7 +7365,681 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de Flows and Stages Procedures & Fases + + + Failed to fetch + + + External + + + Service account + + + Service account (internal) + + + New version available + + + Brand + + + Failed to fetch data. + + + Check the release notes + + + User Statistics + + + Users created + + + Failed logins + + + Submit + + + Internal application name used in URLs. + + + UI Settings + + + OAuth2/OIDC (Open Authorization/OpenID Connect) + + + LDAP (Lightweight Directory Access Protocol) + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth (Single Application) + + + For nginx's auth_request or traefik's forwardAuth + + + Forward Auth (Domain Level) + + + For nginx's auth_request or traefik's forwardAuth per root domain + + + SAML (Security Assertion Markup Language) + + + Configure SAML provider manually + + + RADIUS (Remote Authentication Dial-In User Service) + + + Configure RADIUS provider manually + + + SCIM (System for Cross-domain Identity Management) + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + There was an error in the application. + + + Review the application. + + + There was an error in the provider. + + + Review the provider. + + + There was an error + + + There was an error creating the application, but no error message was sent. Please review the server logs. + + + Configure LDAP Provider + + + Method's display Name. + + + Configure OAuth2/OpenId Provider + + + Configure Proxy Provider + + + AdditionalScopes + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + + + Configure Radius Provider + + + Configure SAML Provider + + + Property mappings used for user mapping. + + + Configure SCIM Provider + + + Property mappings used for group creation. + + + Create With Wizard + + + Don't show this message again. + + + One hint, 'New Application Wizard', is currently hidden + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. + + + Provider require enterprise. + + + Learn more + + + Connection expiry + + + Determines how long a session lasts before being disconnected and requiring re-authorization. + + + Connection settings. + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Permission(s) + + + Permission + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + RBAC is in preview. + + + User Object Permissions + + + Role Object Permissions + + + Successfully updated endpoint. + + + Successfully created endpoint. + + + Protocol + + + RDP + + + SSH + + + VNC + + + Host + + + Hostname/IP to connect to. + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Failure result + + + Pass + + + Don't pass + + + Result used when policy execution fails. + + + 0: Too guessable: risky password. (guesses &lt; 10^3) + + + 1: Very guessable: protection from throttled online attacks. (guesses &lt; 10^6) + + + 2: Somewhat guessable: protection from unthrottled online attacks. (guesses &lt; 10^8) + + + 3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses &lt; 10^10) + + + 4: Very unguessable: strong protection from offline slow-hash scenario. (guesses &gt;= 10^10) + + + Successfully created user and added to group + + + Update Permissions + + + Endpoint(s) + + + Update Endpoint + + + These bindings control which users will have access to this endpoint. Users must also have access to the application. + + + Create Endpoint + + + RAC is in preview. + + + Update RAC Provider + + + Endpoints + + + No sync status. + + + Sync currently running. + + + External applications that use authentik as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. + + + Also known as Client ID. + + + Also known as Client Secret. + + + Global status + + + Vendor + + + Connectivity + + + General settings + + + RDP settings + + + Ignore server certificate + + + Enable wallpaper + + + Enable font-smoothing + + + Enable full window dragging + + + The token has been copied to your clipboard + + + The token was displayed because authentik does not have permission to write to the clipboard + + + Editing is disabled for managed tokens + + + Successfully updated brand. + + + Successfully created brand. + + + Use this brand for each domain that doesn't have a dedicated brand. + + + Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this brand. + + + Brands + + + Brand(s) + + + Update Brand + + + Create Brand + + + To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + + This user will be added to the group "". + + + Pseudolocale (for testing) + + + Korean + + + Dutch + + + A copy of this recovery link has been placed in your clipboard + + + The current brand must have a recovery flow configured to use a recovery link + + + <No name set> + + + Create recovery link + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + User doesn't have view permission so description cannot be retrieved. + + + Lock the user out of this system + + + Allow the user to log in and use this system + + + Temporarily assume the identity of this user + + + Enter a new password for this user + + + Create a link for this user to reset their password + + + Create Recovery Link + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Custom attributes + + + Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). + + + Required: User verification must occur. + + + Preferred: User verification is preferred if available, but not required. + + + Discouraged: User verification should not occur. + + + Required: The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur + + + Preferred: The authenticator can create and store a dedicated credential, but if it doesn't that's alright too + + + Discouraged: The authenticator should not create a dedicated credential + + + Deny message + + + Message shown when this stage is run. + + + Pretend user exists + + + When enabled, the stage will always accept the given user identifier and continue. + + + Network binding + + + No binding + + + Bind ASN + + + Bind ASN and Network + + + Bind ASN, Network and IP + + + Configure if sessions created by this stage should be bound to the Networks they were created in. + + + GeoIP binding + + + Bind Continent + + + Bind Continent and Country + + + Bind Continent, Country and City + + + Configure if sessions created by this stage should be bound to their GeoIP-based location + + + User type used for newly created users. + + + Require Outpost (flow can only be executed from an outpost). + + + Flow Info + + + Event volume + + + RAC + + + Successfully updated settings. + + + Avatars + + + Configure how authentik should show avatars for users. The following values can be set: + + + Disables per-user avatars and just shows a 1x1 pixel transparent picture + + + Uses gravatar with the user's email address + + + Generated avatars based on the user's name + + + Any URL: If you want to use images hosted on another server, you can set any URL. Additionally, these placeholders can be used: + + + The user's username + + + The email address, md5 hashed + + + The user's UPN, if set (otherwise an empty string) + + + An attribute path like + attributes.something.avatar, which can be used in + combination with the file field to allow users to upload custom + avatars for themselves. + + + Multiple values can be set, comma-separated, and authentik will fallback to the next mode when no avatar could be found. + + + For example, setting this to gravatar,initials will + attempt to get an avatar from Gravatar, and if the user has not + configured on there, it will fallback to a generated avatar. + + + Default user change name + + + Enable the ability for users to change their name. + + + Default user change email + + + Enable the ability for users to change their email. + + + Default user change username + + + Enable the ability for users to change their username. + + + Footer links + + + This option configures the footer links on the flow executor pages. It must be a valid JSON list and can be used as follows: + + + GDPR compliance + + + When enabled, all the events caused by a user will be deleted upon the user's deletion. + + + Impersonation + + + Globally enable/disable impersonation. + + + System settings + + + WebAuthn requires this page to be accessed via HTTPS. + + + WebAuthn not supported by browser. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard + + + Connection failed after attempts. + + + Re-connecting in second(s). + + + Connecting... + + + Select endpoint to connect to - \ No newline at end of file + diff --git a/web/xliff/pl.xlf b/web/xliff/pl.xlf index 1c63dc4f2..20f031b4f 100644 --- a/web/xliff/pl.xlf +++ b/web/xliff/pl.xlf @@ -6315,6 +6315,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -6345,6 +6357,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/pseudo-LOCALE.xlf b/web/xliff/pseudo-LOCALE.xlf index c870f2288..1c30e2130 100644 --- a/web/xliff/pseudo-LOCALE.xlf +++ b/web/xliff/pseudo-LOCALE.xlf @@ -8040,6 +8040,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -8070,6 +8082,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/tr.xlf b/web/xliff/tr.xlf index 6a5ee1e02..faf738130 100644 --- a/web/xliff/tr.xlf +++ b/web/xliff/tr.xlf @@ -6101,6 +6101,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -6131,6 +6143,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/zh-CN.xlf b/web/xliff/zh-CN.xlf new file mode 100644 index 000000000..e0af65466 --- /dev/null +++ b/web/xliff/zh-CN.xlf @@ -0,0 +1,5148 @@ + + + + + + Admin + + + Open API drawer + + + Open Notification drawer + + + Connection error, reconnecting... + + + Loading... + + + Application + + + Logins + + + Failed to fetch + + + Click to change value + + + Select an object. + + + Loading options... + + + API Access + + + App password + + + Recovery + + + Verification + + + Unknown intent + + + Login + + + Failed login + + + Logout + + + User was written to + + + Suspicious request + + + Password set + + + Secret was viewed + + + Secret was rotated + + + Invitation used + + + Application authorized + + + Source linked + + + Impersonation started + + + Impersonation ended + + + Flow execution + + + Policy execution + + + Policy exception + + + Property Mapping exception + + + System task execution + + + System task exception + + + General system exception + + + Configuration error + + + Model created + + + Model updated + + + Model deleted + + + Email sent + + + Update available + + + Alert + + + Notice + + + Warning + + + Unknown severity + + + Static tokens + + + TOTP Device + + + Internal + + + External + + + Service account + + + Service account (internal) + + + Show less + + + Show more + + + UID + + + Name + + + App + + + Model Name + + + Message + + + Subject + + + From + + + To + + + Context + + + User + + + Affected model: + + + Authorized application: + + + Using flow + + + Email info: + + + Secret: + + + Exception + + + Open issue on GitHub... + + + Expression + + + Binding + + + Request + + + Object + + + Result + + + Passing + + + Messages + + + New version available + + + Using source + + + Attempted to log in as + + + No additional data available. + + + no tabs defined + + + Remove item + + + - of + + + Go to previous page + + + Go to next page + + + Search... + + + Loading + + + No objects found. + + + Failed to fetch objects. + + + Refresh + + + Select all rows + + + Action + + + Creation Date + + + Client IP + + + Brand + + + Recent events + + + On behalf of + + + - + + + No Events found. + + + No matching events could be found. + + + Embedded outpost is not configured correctly. + + + Check outposts. + + + HTTPS is not detected correctly + + + Server and client are further than 5 seconds apart. + + + OK + + + Everything is ok. + + + System status + + + Based on + + + is available! + + + Up-to-date! + + + Version + + + Workers + + + No workers connected. Background tasks will not run. + + + hour(s) ago + + + Failed to fetch data. + + + day(s) ago + + + Authorizations + + + Failed Logins + + + Successful Logins + + + : + + + Cancel + + + LDAP Source + + + SCIM Provider + + + Healthy + + + Failed + + + Unsynced / N/A + + + Healthy outposts + + + Outdated outposts + + + Unhealthy outposts + + + Not found + + + The URL "" was not found. + + + Return home + + + General system status + + + Welcome, . + + + Quick actions + + + Create a new application + + + Check the logs + + + Explore integrations + + + Manage users + + + Check the release notes + + + Outpost status + + + Sync status + + + Logins and authorizations over the last week (per 8 hours) + + + Apps with most usage + + + days ago + + + Objects created + + + User Statistics + + + Users created per day in the last month + + + Users created + + + Logins per day in the last month + + + Failed Logins per day in the last month + + + Failed logins + + + Clear search + + + System Tasks + + + Long-running operations which authentik executes in the background. + + + Identifier + + + Description + + + Last run + + + Status + + + Actions + + + Successful + + + Error + + + Unknown + + + Duration + + + seconds + + + Restart task + + + Close + + + Create + + + Next + + + Back + + + Submit + + + Type + + + Select providers to add to application + + + Add + + + Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". + + + Path template for users created. Use placeholders like `%(slug)s` to insert the source slug. + + + Currently set to: + + + No form found + + + Form didn't return a promise for submitting + + + Any policy must match to grant access + + + All policies must match to grant access + + + Successfully updated application. + + + Successfully created application. + + + Application's display Name. + + + Slug + + + Internal application name used in URLs. + + + Group + + + Optionally enter a group name. Applications with identical groups are shown grouped together. + + + Provider + + + Select a provider that this application should use. + + + Backchannel Providers + + + Select backchannel providers which augment the functionality of the main provider. + + + Add provider + + + Policy engine mode + + + UI settings + + + Launch URL + + + If left empty, authentik will try to extract the launch URL based on the selected provider. + + + Open in new tab + + + If checked, the launch URL will open in a new browser tab or window from the user's application library. + + + Icon + + + Clear icon + + + Delete currently set icon. + + + Publisher + + + UI Settings + + + OAuth2/OIDC (Open Authorization/OpenID Connect) + + + Modern applications, APIs and Single-page applications. + + + LDAP (Lightweight Directory Access Protocol) + + + Provide an LDAP interface for applications and users to authenticate against. + + + Transparent Reverse Proxy + + + For transparent reverse proxies with required authentication + + + Forward Auth (Single Application) + + + For nginx's auth_request or traefik's forwardAuth + + + Forward Auth (Domain Level) + + + For nginx's auth_request or traefik's forwardAuth per root domain + + + SAML (Security Assertion Markup Language) + + + Configure SAML provider manually + + + RADIUS (Remote Authentication Dial-In User Service) + + + Configure RADIUS provider manually + + + SCIM (System for Cross-domain Identity Management) + + + Configure SCIM provider manually + + + Saving Application... + + + Authentik was unable to save this application: + + + Your application has been saved + + + There was an error in the application. + + + Review the application. + + + There was an error in the provider. + + + Review the provider. + + + There was an error + + + There was an error creating the application, but no error message was sent. Please review the server logs. + + + Authentication + + + Authorization + + + Enrollment + + + Invalidation + + + Stage Configuration + + + Unenrollment + + + Unknown designation + + + Stacked + + + Content left + + + Content right + + + Sidebar left + + + Sidebar right + + + Unknown layout + + + Cached binding + + + Flow is executed and session is cached in memory. Flow is executed when session expires + + + Direct binding + + + Always execute the configured bind flow to authenticate the user + + + Cached querying + + + The outpost holds all users and groups in-memory and will refresh every 5 Minutes + + + Direct querying + + + Always returns the latest data, but slower than cached querying + + + When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon. + + + The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber + + + The certificate for the above configured Base DN. As a fallback, the provider uses a self-signed certificate. + + + DNS name for which the above configured certificate should be used. The certificate cannot be detected based on the base DN, as the SSL/TLS negotiation happens before such data is exchanged. + + + The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber + + + Configure LDAP Provider + + + Method's display Name. + + + Bind flow + + + Flow used for users to authenticate. + + + Search group + + + Bind mode + + + Configure how the outpost authenticates requests. + + + Search mode + + + Configure how the outpost queries the core authentik server's users. + + + Code-based MFA Support + + + Protocol settings + + + Base DN + + + LDAP DN under which bind requests and search requests can be made. + + + Certificate + + + TLS Server name + + + UID start number + + + GID start number + + + Successfully updated provider. + + + Successfully created provider. + + + (Format: hours=-1;minutes=-2;seconds=-3). + + + (Format: hours=1;minutes=2;seconds=3). + + + The following keywords are supported: + + + Confidential + + + Confidential clients are capable of maintaining the confidentiality of their credentials such as client secrets + + + Public + + + Public clients are incapable of maintaining the confidentiality and should use methods like PKCE. + + + Based on the User's hashed ID + + + Based on the User's ID + + + Based on the User's UUID + + + Based on the User's username + + + Based on the User's Email + + + This is recommended over the UPN mode. + + + Based on the User's UPN + + + Requires the user to have a 'upn' attribute set, and falls back to hashed user ID. Use this mode only if you have different UPN and Mail domains. + + + Each provider has a different issuer, based on the application slug + + + Same identifier is used for all providers + + + Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows. + + + If no explicit redirect URIs are specified, the first successfully used redirect URI will be saved. + + + To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. + + + Authentication flow + + + Flow used when a user access this provider and is not authenticated. + + + Authorization flow + + + Flow used when authorizing this provider. + + + Client type + + + Client ID + + + Client Secret + + + Redirect URIs/Origins (RegEx) + + + Signing Key + + + Key used to sign the tokens. + + + Advanced protocol settings + + + Access code validity + + + Configure how long access codes are valid for. + + + Access Token validity + + + Configure how long access tokens are valid for. + + + Refresh Token validity + + + Configure how long refresh tokens are valid for. + + + Scopes + + + Select which scopes can be used by the client. The client still has to specify the scope to access the data. + + + Hold control/command to select multiple items. + + + Subject mode + + + Configure what data should be used as unique User Identifier. For most cases, the default should be fine. + + + Include claims in id_token + + + Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint. + + + Issuer mode + + + Configure how the issuer field of the ID Token should be filled. + + + Machine-to-Machine authentication settings + + + Trusted OIDC Sources + + + JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider. + + + Configure OAuth2/OpenId Provider + + + HTTP-Basic Username Key + + + User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used. + + + HTTP-Basic Password Key + + + User/Group Attribute used for the password part of the HTTP-Basic Header. + + + Configure Proxy Provider + + + Token validity + + + Configure how long tokens are valid for. + + + AdditionalScopes + + + Additional scope mappings, which are passed to the proxy. + + + Unauthenticated URLs + + + Unauthenticated Paths + + + Regular expressions for which authentication is not required. Each new line is interpreted as a new expression. + + + When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions. + + + Authentication settings + + + Intercept header authentication + + + When enabled, authentik will intercept the Authorization header to authenticate the request. + + + Send HTTP-Basic Authentication + + + Send a custom HTTP-Basic Authentication header based on values from authentik. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. + + + An example setup can look like this: + + + authentik running on auth.example.com + + + app1 running on app1.example.com + + + In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com. + + + External host + + + The external URL you'll authenticate at. The authentik core server should be reachable under this URL. + + + Cookie domain + + + Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'. + + + This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well. + + + The external URL you'll access the application at. Include any non-standard port. + + + Internal host + + + Upstream host that the requests are forwarded to. + + + Internal host SSL Validation + + + Validate SSL Certificates of upstream servers. + + + Use this provider with nginx's auth_request or traefik's + forwardAuth. Each application/domain needs its own provider. + Additionally, on each domain, /outpost.goauthentik.io must be + routed to the outpost (when using a managed outpost, this is done for you). + + + Configure Radius Provider + + + Shared secret + + + Client Networks + + + List of CIDRs (comma-seperated) that clients can connect from. A more specific + CIDR will match before a looser one. Clients connecting from a non-specified CIDR + will be dropped. + + + Redirect + + + Post + + + Configure SAML Provider + + + ACS URL + + + Issuer + + + Also known as EntityID. + + + Service Provider Binding + + + Determines how authentik sends the response back to the Service Provider. + + + Audience + + + Signing Certificate + + + Certificate used to sign outgoing Responses going to the Service Provider. + + + Verification Certificate + + + When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default. + + + Property Mappings + + + Property mappings used for user mapping. + + + NameID Property Mapping + + + Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected. + + + Assertion valid not before + + + Configure the maximum allowed time drift for an assertion. + + + Assertion valid not on or after + + + Assertion not valid on or after current time + this value. + + + Session valid not on or after + + + Session not valid on or after current time + this value. + + + Digest algorithm + + + Signature algorithm + + + Configure SCIM Provider + + + URL + + + SCIM base url, usually ends in /v2. + + + Token + + + Token to authenticate with. Currently only bearer authentication is supported. + + + User filtering + + + Exclude service accounts + + + Only sync users within the selected group. + + + Attribute mapping + + + User Property Mappings + + + Group Property Mappings + + + Property mappings used for group creation. + + + Create With Wizard + + + New application + + + Don't show this message again. + + + One hint, 'New Application Wizard', is currently hidden + + + Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed. + + + Proxy + + + Forward auth (single application) + + + Forward auth (domain level) + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Authentication URL + + + Unknown proxy mode + + + Additional scopes + + + Property mappings + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. + + + Successfully imported provider. + + + Metadata + + + Apply changes + + + Finish + + + Select type + + + Try the new application wizard + + + The new application wizard greatly simplifies the steps required to create applications and providers. + + + Try it now + + + Provider require enterprise. + + + Learn more + + + New provider + + + Create a new provider. + + + Create + + + Connection expiry + + + Determines how long a session lasts before being disconnected and requiring re-authorization. + + + Settings + + + Connection settings. + + + Property mappings used to user mapping. + + + Property mappings used to group creation. + + + Not used by any other object. + + + object will be DELETED + + + connection will be deleted + + + reference will be reset to default value + + + reference will be set to an empty value + + + () + + + ID + + + Successfully deleted + + + Failed to delete : + + + Delete + + + Are you sure you want to delete ? + + + Delete + + + Providers + + + Provide support for protocols like SAML and OAuth to assigned applications. + + + Provider(s) + + + Assigned to application + + + Assigned to application (backchannel) + + + Warning: Provider not assigned to any application. + + + Update + + + Update + + + Edit + + + Create Application + + + Successfully assigned permission. + + + Role + + + Assign + + + Assign permission to role + + + Assign to new role + + + Permission(s) + + + Permission + + + Directly assigned + + + Assign permission to user + + + Assign to new user + + + Superuser + + + RBAC is in preview. + + + Send us feedback! + + + User Object Permissions + + + Role Object Permissions + + + Overview + + + Changelog + + + Permissions + + + Warning: Provider is not used by any Outpost. + + + Assigned to application + + + Update LDAP Provider + + + How to connect + + + Connect to the LDAP Server on port 389: + + + Check the IP of the Kubernetes service, or + + + The Host IP of the docker host + + + Bind DN + + + Bind Password + + + Search base + + + Preview + + + Warning: Provider is not used by an Application. + + + Redirect URIs + + + Update OAuth2 Provider + + + OpenID Configuration URL + + + OpenID Configuration Issuer + + + Authorize URL + + + Token URL + + + Userinfo URL + + + Logout URL + + + JWKS URL + + + Example JWT payload (for currently authenticated user) + + + Yes + + + No + + + Forward auth (domain-level) + + + Nginx (Ingress) + + + Nginx (Proxy Manager) + + + Nginx (standalone) + + + Traefik (Ingress) + + + Traefik (Compose) + + + Traefik (Standalone) + + + Caddy (Standalone) + + + Internal Host + + + External Host + + + Basic-Auth + + + Mode + + + Update Proxy Provider + + + Protocol Settings + + + Allowed Redirect URIs + + + Setup + + + No additional setup is required. + + + Successfully updated endpoint. + + + Successfully created endpoint. + + + Protocol + + + RDP + + + SSH + + + VNC + + + Host + + + Hostname/IP to connect to. + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + + + Advanced settings + + + Active + + + Last login + + + Select users to add + + + Successfully updated group. + + + Successfully created group. + + + Is superuser + + + Users added to this group will be superusers. + + + Parent + + + Roles + + + Select roles to grant this groups' users' permissions from the selected roles. + + + Attributes + + + Set custom attributes using YAML or JSON. + + + Successfully updated binding. + + + Successfully created binding. + + + Policy + + + Group mappings can only be checked if a user is already logged in when trying to access this source. + + + User mappings can only be checked if a user is already logged in when trying to access this source. + + + Enabled + + + Negate result + + + Negates the outcome of the binding. Messages are unaffected. + + + Order + + + Timeout + + + Failure result + + + Pass + + + Don't pass + + + Result used when policy execution fails. + + + Successfully updated policy. + + + Successfully created policy. + + + A policy used for testing. Always returns the same result as specified below after waiting a random duration. + + + Execution logging + + + When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged. + + + Policy-specific settings + + + Pass policy? + + + Wait (min) + + + The policy takes a random time to execute. This controls the minimum time it will take. + + + Wait (max) + + + Matches an event against a set of criteria. If any of the configured values match, the policy passes. + + + Match created events with this action type. When left empty, all action types will be matched. + + + Matches Event's Client IP (strict matching, for network matching use an Expression Policy. + + + Match events created by selected application. When left empty, all applications are matched. + + + Model + + + Match events created by selected model. When left empty, all models are matched. + + + Checks if the request's user's password has been changed in the last x days, and denys based on settings. + + + Maximum age (in days) + + + Only fail the policy, don't invalidate user's password + + + Executes the python snippet to determine whether to allow or deny a request. + + + Expression using Python. + + + See documentation for a list of all variables. + + + Static rules + + + Minimum length + + + Minimum amount of Uppercase Characters + + + Minimum amount of Lowercase Characters + + + Minimum amount of Digits + + + Minimum amount of Symbols Characters + + + Error message + + + Symbol charset + + + Characters which are considered as symbols. + + + HaveIBeenPwned settings + + + Allowed count + + + Allow up to N occurrences in the HIBP database. + + + zxcvbn settings + + + Score threshold + + + If the password's score is less than or equal this value, the policy will fail. + + + 0: Too guessable: risky password. (guesses &lt; 10^3) + + + 1: Very guessable: protection from throttled online attacks. (guesses &lt; 10^6) + + + 2: Somewhat guessable: protection from unthrottled online attacks. (guesses &lt; 10^8) + + + 3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses &lt; 10^10) + + + 4: Very unguessable: strong protection from offline slow-hash scenario. (guesses &gt;= 10^10) + + + Checks the value from the policy request against several rules, mostly used to ensure password strength. + + + Password field + + + Field key to check, field keys defined in Prompt stages are available. + + + Check static rules + + + Check haveibeenpwned.com + + + For more info see: + + + Check zxcvbn + + + Password strength estimator created by Dropbox, see: + + + Allows/denys requests based on the users and/or the IPs reputation. + + + Invalid login attempts will decrease the score for the client's IP, and the +username they are attempting to login as, by one. + + + The policy passes when the reputation score is below the threshold, and +doesn't pass when either or both of the selected options are equal or above the threshold. + + + Check IP + + + Check Username + + + Threshold + + + New policy + + + Create a new policy. + + + Create Binding + + + Members + + + Select groups to add user to + + + Warning: Adding the user to the selected group(s) will give them superuser permissions. + + + Successfully updated user. + + + Successfully created user and added to group + + + Successfully created user. + + + Username + + + User's primary identifier. 150 characters or fewer. + + + User's display name. + + + User type + + + Internal users might be users such as company employees, which will get access to the full Enterprise feature set. + + + External users might be external consultants or B2C customers. These users don't get access to enterprise features. + + + Service accounts should be used for machine-to-machine authentication or other automations. + + + Email + + + Is active + + + Designates whether this user should be treated as active. Unselect this instead of deleting accounts. + + + Path + + + Policy / User / Group + + + Policy + + + Group + + + User + + + Edit Policy + + + Update Group + + + Edit Group + + + Update User + + + Edit User + + + Policy binding(s) + + + Update Binding + + + Edit Binding + + + No Policies bound. + + + No policies are currently bound to this object. + + + Create and bind Policy + + + Bind existing policy + + + Update Permissions + + + Endpoint(s) + + + Update Endpoint + + + These bindings control which users will have access to this endpoint. Users must also have access to the application. + + + Create Endpoint + + + RAC is in preview. + + + Update RAC Provider + + + Endpoints + + + Update Radius Provider + + + Download + + + Copy download URL + + + Download signing certificate + + + Related objects + + + Update SAML Provider + + + SAML Configuration + + + EntityID/Issuer + + + SSO URL (Post) + + + SSO URL (Redirect) + + + SSO URL (IdP-initiated Login) + + + SLO URL (Post) + + + SLO URL (Redirect) + + + SAML Metadata + + + Example SAML attributes + + + NameID attribute + + + No sync status. + + + Sync currently running. + + + Not synced yet. + + + Task finished with warnings + + + Task finished with errors + + + Last sync: + + + Warning: Provider is not assigned to an application as backchannel provider. + + + Update SCIM Provider + + + Run sync again + + + Application Icon + + + Applications + + + External applications that use authentik as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access. + + + Provider Type + + + Application(s) + + + Update Application + + + Open + + + Successfully sent test-request. + + + Log messages + + + No log messages. + + + Warning: Application is not used by any Outpost. + + + Related + + + Check access + + + Check + + + Check Application access + + + Test + + + Launch + + + Logins over the last week (per 8 hours) + + + Policy / Group / User Bindings + + + These policies control which users can access this application. + + + Successfully updated source. + + + Successfully created source. + + + Sync users + + + User password writeback + + + Login password is synced from LDAP into authentik automatically. Enable this option only to write password changes in authentik back to LDAP. + + + Sync groups + + + Connection settings + + + Server URI + + + Specify multiple server URIs by separating them with a comma. + + + Enable StartTLS + + + To use SSL instead, use 'ldaps://' and disable this option. + + + Use Server URI for SNI verification + + + Required for servers using TLS 1.3+ + + + TLS Verification Certificate + + + When connecting to an LDAP Server with TLS, certificates are not checked by default. Specify a keypair to validate the remote certificate. + + + TLS Client authentication certificate + + + Client certificate keypair to authenticate against the LDAP Server's Certificate. + + + Bind CN + + + LDAP Attribute mapping + + + Property mappings used to user creation. + + + Additional settings + + + Parent group for all the groups imported from LDAP. + + + User path + + + Addition User DN + + + Additional user DN, prepended to the Base DN. + + + Addition Group DN + + + Additional group DN, prepended to the Base DN. + + + User object filter + + + Consider Objects matching this filter to be Users. + + + Group object filter + + + Consider Objects matching this filter to be Groups. + + + Group membership field + + + Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' + + + Object uniqueness field + + + Field which contains a unique Identifier. + + + Link users on unique identifier + + + Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses + + + Use the user's email address, but deny enrollment when the email address already exists + + + Link to a user with identical username. Can have security implications when a username is used with another source + + + Use the user's username, but deny enrollment when the username already exists + + + Unknown user matching mode + + + URL settings + + + Authorization URL + + + URL the user is redirect to to consent the authorization. + + + Access token URL + + + URL used by authentik to retrieve tokens. + + + Profile URL + + + URL used by authentik to get user information. + + + Request token URL + + + URL used to request the initial token. This URL is only required for OAuth 1. + + + OIDC Well-known URL + + + OIDC well-known configuration URL. Can be used to automatically configure the URLs above. + + + OIDC JWKS URL + + + JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source. + + + OIDC JWKS + + + Raw JWKS data. + + + User matching mode + + + Consumer key + + + Also known as Client ID. + + + Consumer secret + + + Also known as Client Secret. + + + Additional scopes to be passed to the OAuth Provider, separated by space. To replace existing scopes, prefix with *. + + + Flow settings + + + Flow to use when authenticating existing users. + + + Enrollment flow + + + Flow to use when enrolling new users. + + + Load servers + + + Re-authenticate with plex + + + Allow friends to authenticate via Plex, even if you don't share any servers + + + Allowed servers + + + Select which server a user has to be a member of to be allowed to authenticate. + + + SSO URL + + + URL that the initial Login request is sent to. + + + SLO URL + + + Optional URL if the IDP supports Single-Logout. + + + Also known as Entity ID. Defaults the Metadata URL. + + + Binding Type + + + Redirect binding + + + Post-auto binding + + + Post binding but the request is automatically sent and the user doesn't have to confirm. + + + Post binding + + + Signing keypair + + + Keypair which is used to sign outgoing requests. Leave empty to disable signing. + + + Allow IDP-initiated logins + + + Allows authentication flows initiated by the IdP. This can be a security risk, as no validation of the request ID is done. + + + NameID Policy + + + Persistent + + + Email address + + + Windows + + + X509 Subject + + + Transient + + + Delete temporary users after + + + Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. + + + Pre-authentication flow + + + Flow used before authentication. + + + New source + + + Create a new source. + + + Federation and Social login + + + Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves. + + + Source(s) + + + Disabled + + + Built-in + + + Global status + + + Vendor + + + Update LDAP Source + + + Connectivity + + + OAuth Source + + + Generic OpenID Connect + + + Unknown provider type + + + Details + + + Callback URL + + + Access Key + + + Update OAuth Source + + + Diagram + + + Policy Bindings + + + These bindings control which users can access this source. + You can only use policies here as access is checked before the user is authenticated. + + + Update Plex Source + + + Update SAML Source + + + Successfully updated mapping. + + + Successfully created mapping. + + + Object field + + + Field of the user object this value is written to. + + + General settings + + + Password + + + RDP settings + + + Ignore server certificate + + + Enable wallpaper + + + Enable font-smoothing + + + Enable full window dragging + + + SAML Attribute Name + + + Attribute name used for SAML Assertions. Can be a URN OID, a schema reference, or a any other string. If this property mapping is used for NameID Property, this field is discarded. + + + Friendly Name + + + Optionally set the 'FriendlyName' value of the Assertion attribute. + + + Scope name + + + Scope which the client can specify to access these properties. + + + Description shown to the user when consenting. If left empty, the user won't be informed. + + + Example context data + + + Active Directory User + + + Active Directory Group + + + New property mapping + + + Create a new property mapping. + + + Control how authentik exposes and interprets information. + + + Property Mapping(s) + + + Test Property Mapping + + + Hide managed mappings + + + Successfully updated token. + + + Successfully created token. + + + Expires on + + + Unique identifier the token is referenced by. + + + Intent + + + API Token + + + Used to access the API programmatically + + + App password. + + + Used to login using a flow executor + + + Expiring + + + If this is selected, the token will expire. Upon expiration, the token will be rotated. + + + The token has been copied to your clipboard + + + The token was displayed because authentik does not have permission to write to the clipboard + + + Tokens + + + Tokens are used throughout authentik for Email validation stages, Recovery keys and API access. + + + Expires? + + + Expiry date + + + Token(s) + + + Create Token + + + Token is managed by authentik. + + + Update Token + + + Editing is disabled for managed tokens + + + Copy token + + + Successfully updated brand. + + + Successfully created brand. + + + Domain + + + Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match. + + + Default + + + Use this brand for each domain that doesn't have a dedicated brand. + + + Branding settings + + + Title + + + Branding shown in page title and several other places. + + + Logo + + + Icon shown in sidebar/header and flow executor. + + + Favicon + + + Icon shown in the browser tab. + + + Default flows + + + Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used. + + + Invalidation flow + + + Flow used to logout. If left empty, the first applicable flow sorted by the slug is used. + + + Recovery flow + + + Recovery flow. If left empty, the first applicable flow sorted by the slug is used. + + + Unenrollment flow + + + If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown. + + + User settings flow + + + If set, users are able to configure details of their profile. + + + Device code flow + + + If set, the OAuth Device Code profile can be used, and the selected flow will be used to enter the code. + + + Other global settings + + + Web Certificate + + + Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this brand. + + + Brands + + + Configure visual settings and defaults for different domains. + + + Default? + + + Brand(s) + + + Update Brand + + + Create Brand + + + Policies + + + Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages. + + + Assigned to object(s). + + + Warning: Policy is not assigned. + + + Test Policy + + + Policy / Policies + + + Successfully cleared policy cache + + + Failed to delete policy cache + + + Clear cache + + + Clear Policy cache + + + Are you sure you want to clear the policy cache? This will cause all policies to be re-evaluated on their next usage. + + + Reputation scores + + + Reputation for IP and user identifiers. Scores are decreased for each failed login and increased for each successful login. + + + IP + + + Score + + + Updated + + + Reputation + + + Groups + + + Group users together and give them permissions based on the membership. + + + Superuser privileges? + + + Group(s) + + + Create Group + + + Create group + + + Enabling this toggle will create a group named after the user, with the user as member. + + + Use the username and password below to authenticate. The password can be retrieved later on the Tokens page. + + + Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List. + + + The following objects use + + + connecting object will be deleted + + + Successfully updated + + + Failed to update : + + + Are you sure you want to update ""? + + + Successfully updated password. + + + Successfully sent email. + + + Email stage + + + Successfully added user(s). + + + Users to add + + + Add users + + + User(s) + + + Remove Users(s) + + + Are you sure you want to remove the selected users from the group ? + + + Remove + + + Impersonate + + + User status + + + Inactive + + + Regular user + + + Change status + + + Deactivate + + + Activate + + + Update password + + + Set password + + + Successfully generated recovery link + + + No recovery flow is configured. + + + Copy recovery link + + + Send link + + + Send recovery link to user + + + Email recovery link + + + Recovery link cannot be emailed, user has no email address saved. + + + To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + + Add User + + + Warning: This group is configured with superuser access. Added users will have superuser access. + + + Add existing user + + + Create user + + + Create User + + + This user will be added to the group "". + + + Create Service account + + + Hide service-accounts + + + Group Info + + + Notes + + + Edit the notes attribute of this group to add notes here. + + + Users + + + Pseudolocale (for testing) + + + English + + + Spanish + + + German + + + French + + + Korean + + + Dutch + + + Polish + + + Turkish + + + Chinese (traditional) + + + Taiwanese Mandarin + + + Chinese (simplified) + + + Warning: The current user count has exceeded the configured licenses. + + + Click here for more info. + + + API Requests + + + Open API Browser + + + Show details + + + Notifications + + + unread + + + Successfully cleared notifications + + + Clear all + + + User interface + + + Dashboards + + + Outposts + + + Events + + + Logs + + + Notification Rules + + + Notification Transports + + + Customisation + + + Blueprints + + + Flows and Stages + + + Flows + + + Stages + + + Prompts + + + Directory + + + Tokens and App passwords + + + Invitations + + + System + + + Certificates + + + Outpost Integrations + + + A newer version of the frontend is available. + + + You're currently impersonating . Click to stop. + + + Enterprise + + + Licenses + + + Root + + + A copy of this recovery link has been placed in your clipboard + + + The current brand must have a recovery flow configured to use a recovery link + + + Warning: You're about to delete the user you're logged in as (). Proceed at your own risk. + + + Hide deactivated user + + + <No name set> + + + Create recovery link + + + User folders + + + Successfully added user to group(s). + + + Groups to add + + + Add group + + + Remove from Group(s) + + + Are you sure you want to remove user from the following groups? + + + Add Group + + + Add to existing group + + + Add new group + + + Application authorizations + + + Select permissions to grant + + + Permissions to add + + + Select permissions + + + Assign permission + + + User doesn't have view permission so description cannot be retrieved. + + + Revoked? + + + Expires + + + ID Token + + + Refresh Tokens(s) + + + Last IP + + + Session(s) + + + Expiry + + + (Current session) + + + Consent(s) + + + Confirmed + + + Device(s) + + + User Info + + + Lock the user out of this system + + + Allow the user to log in and use this system + + + Temporarily assume the identity of this user + + + Enter a new password for this user + + + Create a link for this user to reset their password + + + Create Recovery Link + + + Actions over the last week (per 8 hours) + + + Edit the notes attribute of this user to add notes here. + + + Sessions + + + User events + + + Explicit Consent + + + OAuth Refresh Tokens + + + MFA Authenticators + + + Assigned permissions + + + Assigned global permissions + + + Assigned object permissions + + + Successfully updated role. + + + Successfully created role. + + + Manage roles which grant permissions to objects within authentik. + + + Role(s) + + + Update Role + + + Create Role + + + Role doesn't have view permission so description cannot be retrieved. + + + Role + + + Role Info + + + Successfully updated invitation. + + + Successfully created invitation. + + + Flow + + + When selected, the invite will only be usable with the flow. By default the invite is accepted on all flows with invitation stages. + + + Custom attributes + + + Optional data which is loaded into the flow's 'prompt_data' context variable. YAML or JSON. + + + Single use + + + When enabled, the invitation will be deleted after usage. + + + Select an enrollment flow + + + Link to use the invitation. + + + Create Invitation Links to enroll Users, and optionally force specific attributes of their account. + + + Created by + + + Invitation(s) + + + Invitation not limited to any flow, and can be used with any enrollment flow. + + + Update Invitation + + + Create Invitation + + + Warning: No invitation stage is bound to any flow. Invitations will not work as expected. + + + Auto-detect (based on your browser) + + + Required. + + + Continue + + + Successfully updated prompt. + + + Successfully created prompt. + + + Text: Simple Text input + + + Text Area: Multiline text input + + + Text (read-only): Simple Text input, but cannot be edited. + + + Text Area (read-only): Multiline text input, but cannot be edited. + + + Username: Same as Text input, but checks for and prevents duplicate usernames. + + + Email: Text field with Email type. + + + Password: Masked input, multiple inputs of this type on the same prompt need to be identical. + + + Number + + + Checkbox + + + Radio Button Group (fixed choice) + + + Dropdown (fixed choice) + + + Date + + + Date Time + + + File + + + Separator: Static Separator Line + + + Hidden: Hidden field, can be used to insert data into form. + + + Static: Static value, displayed as-is. + + + authentik: Locale: Displays a list of locales authentik supports. + + + Preview errors + + + Data preview + + + Unique name of this field, used for selecting fields in prompt stages. + + + Field Key + + + Name of the form field, also used to store the value. + + + When used in conjunction with a User Write stage, use attributes.foo to write attributes. + + + Label + + + Label shown next to/above the prompt. + + + Required + + + Interpret placeholder as expression + + + When checked, the placeholder will be evaluated in the same way a property mapping is. + If the evaluation fails, the placeholder itself is returned. + + + Placeholder + + + Optionally provide a short hint that describes the expected input value. + When creating a fixed choice field, enable interpreting as expression and return a + list to return multiple choices. + + + Interpret initial value as expression + + + When checked, the initial value will be evaluated in the same way a property mapping is. + If the evaluation fails, the initial value itself is returned. + + + Initial value + + + Optionally pre-fill the input with an initial value. + When creating a fixed choice field, enable interpreting as expression and + return a list to return multiple default choices. + + + Help text + + + Any HTML can be used. + + + Single Prompts that can be used for Prompt Stages. + + + Field + + + Prompt(s) + + + Update Prompt + + + Create Prompt + + + Target + + + Stage + + + Evaluate when flow is planned + + + Evaluate policies during the Flow planning process. + + + Evaluate when stage is run + + + Evaluate policies before the Stage is present to the user. + + + Invalid response behavior + + + Returns the error message and a similar challenge to the executor + + + Restarts the flow from the beginning + + + Restarts the flow from the beginning, while keeping the flow context + + + Configure how the flow executor should handle an invalid response to a challenge given by this bound stage. + + + Successfully updated stage. + + + Successfully created stage. + + + Stage used to configure a duo-based authenticator. This stage should be used for configuration flows. + + + Authenticator type name + + + Display name of this authenticator, used by users when they enroll an authenticator. + + + API Hostname + + + Duo Auth API + + + Integration key + + + Secret key + + + Duo Admin API (optional) + + + When using a Duo MFA, Access or Beyond plan, an Admin API application can be created. + This will allow authentik to import devices automatically. + + + Stage-specific settings + + + Configuration flow + + + Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage. + + + Twilio Account SID + + + Get this value from https://console.twilio.com + + + Twilio Auth Token + + + Authentication Type + + + Basic Auth + + + Bearer Token + + + External API URL + + + This is the full endpoint to send POST requests to. + + + API Auth Username + + + This is the username to be used with basic auth or the token when used with bearer token + + + API Auth password + + + This is the password to be used with basic auth + + + Mapping + + + Modify the payload sent to the custom provider. + + + Stage used to configure an SMS-based TOTP authenticator. + + + Twilio + + + Generic + + + From number + + + Number the SMS will be sent from. + + + Hash phone number + + + If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons. Devices created from a stage with this enabled cannot be used with the authenticator validation stage. + + + Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows. + + + Token count + + + The number of tokens generated whenever this stage is used. Every token generated per stage execution will be attached to a single static device. + + + Token length + + + The length of the individual generated tokens. Can be increased to improve security. + + + Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator). + + + Digits + + + 6 digits, widely compatible + + + 8 digits, not compatible with apps like Google Authenticator + + + Stage used to validate any authenticator. This stage should be used during authentication or authorization flows. + + + Device classes + + + Static Tokens + + + TOTP Authenticators + + + WebAuthn Authenticators + + + Duo Authenticators + + + SMS-based Authenticators + + + Device classes which can be used to authenticate. + + + Last validation threshold + + + If any of the devices user of the types selected above have been used within this duration, this stage will be skipped. + + + Not configured action + + + Force the user to configure an authenticator + + + Deny the user access + + + WebAuthn User verification + + + User verification must occur. + + + User verification is preferred if available, but not required. + + + User verification should not occur. + + + Configuration stages + + + Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again. + + + When multiple stages are selected, the user can choose which one they want to enroll. + + + Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello). + + + User verification + + + Required: User verification must occur. + + + Preferred: User verification is preferred if available, but not required. + + + Discouraged: User verification should not occur. + + + Resident key requirement + + + Required: The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur + + + Preferred: The authenticator can create and store a dedicated credential, but if it doesn't that's alright too + + + Discouraged: The authenticator should not create a dedicated credential + + + Authenticator Attachment + + + No preference is sent + + + A non-removable authenticator, like TouchID or Windows Hello + + + A "roaming" authenticator, like a YubiKey + + + This stage checks the user's current session against the Google reCaptcha (or compatible) service. + + + Public Key + + + Public key, acquired from https://www.google.com/recaptcha/intro/v3.html. + + + Private Key + + + Private key, acquired from https://www.google.com/recaptcha/intro/v3.html. + + + JS URL + + + URL to fetch JavaScript from, defaults to recaptcha. Can be replaced with any compatible alternative. + + + API URL + + + URL used to validate captcha response, defaults to recaptcha. Can be replaced with any compatible alternative. + + + Prompt for the user's consent. The consent can either be permanent or expire in a defined amount of time. + + + Always require consent + + + Consent given last indefinitely + + + Consent expires. + + + Consent expires in + + + Offset after which consent expires. + + + Statically deny the flow. To use this stage effectively, disable *Evaluate when flow is planned* on the respective binding. + + + Deny message + + + Message shown when this stage is run. + + + Dummy stage used for testing. Shows a simple continue button and always passes. + + + Throw error? + + + SMTP Host + + + SMTP Port + + + SMTP Username + + + SMTP Password + + + Use TLS + + + Use SSL + + + From address + + + Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity. + + + Activate pending user on success + + + When a user returns from the email successfully, their account will be activated. + + + Use global settings + + + When enabled, global Email connection settings will be used and connection settings below will be ignored. + + + Token expiry + + + Time in minutes the token sent is valid. + + + Template + + + Let the user identify themselves with their username or Email address. + + + User fields + + + UPN + + + Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources. + + + Password stage + + + When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks. + + + Case insensitive matching + + + When enabled, user fields are matched regardless of their casing. + + + Pretend user exists + + + When enabled, the stage will always accept the given user identifier and continue. + + + Show matched user + + + When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown. + + + Source settings + + + Sources + + + Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP. + + + Show sources' labels + + + By default, only icons are shown for sources. Enable this to show their full names. + + + Passwordless flow + + + Optional passwordless flow, which is linked at the bottom of the page. When configured, users can use this flow to authenticate with a WebAuthn authenticator, without entering any details. + + + Optional enrollment flow, which is linked at the bottom of the page. + + + Optional recovery flow, which is linked at the bottom of the page. + + + This stage can be included in enrollment flows to accept invitations. + + + Continue flow without invitation + + + If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given. + + + Validate the user's password against the selected backend(s). + + + Backends + + + User database + standard password + + + User database + app passwords + + + User database + LDAP password + + + Selection of backends to test the password against. + + + Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password. + + + Failed attempts before cancel + + + How many attempts a user has before the flow is canceled. To lock the user out, use a reputation policy and a user_write stage. + + + Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable. + + + Fields + + + ("", of type ) + + + Validation Policies + + + Selected policies are executed when the stage is submitted to validate the data. + + + Delete the currently pending user. CAUTION, this stage does not ask for confirmation. Use a consent stage to ensure the user is aware of their actions. + + + Log the currently pending user in. + + + Session duration + + + Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed. + + + Different browsers handle session cookies differently, and might not remove them even when the browser is closed. + + + See here. + + + Stay signed in offset + + + If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. + + + Network binding + + + No binding + + + Bind ASN + + + Bind ASN and Network + + + Bind ASN, Network and IP + + + Configure if sessions created by this stage should be bound to the Networks they were created in. + + + GeoIP binding + + + Bind Continent + + + Bind Continent and Country + + + Bind Continent, Country and City + + + Configure if sessions created by this stage should be bound to their GeoIP-based location + + + Terminate other sessions + + + When enabled, all previous sessions of the user will be terminated. + + + Remove the user from the current session. + + + Write any data from the flow's context's 'prompt_data' to the currently pending user. If no user + is pending, a new user is created, and data is written to them. + + + Never create users + + + When no user is present in the flow context, the stage will fail. + + + Create users when required + + + When no user is present in the the flow context, a new user is created. + + + Always create new users + + + Create a new user even if a user is in the flow context. + + + Create users as inactive + + + Mark newly created users as inactive. + + + User path template + + + User type used for newly created users. + + + Path new users will be created under. If left blank, the default path will be used. + + + Newly created users are added to this group, if a group is selected. + + + New stage + + + Create a new stage. + + + Successfully imported device. + + + The user in authentik this device will be assigned to. + + + Duo User ID + + + The user ID in Duo, can be found in the URL after clicking on a user. + + + Automatic import + + + Successfully imported devices. + + + Start automatic import + + + Or manually import + + + Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow. + + + Stage(s) + + + Import + + + Import Duo device + + + Import devices + + + Successfully updated flow. + + + Successfully created flow. + + + Shown as the Title in Flow pages. + + + Visible in the URL. + + + Designation + + + Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik. + + + No requirement + + + Require authentication + + + Require no authentication. + + + Require superuser. + + + Require Outpost (flow can only be executed from an outpost). + + + Required authentication level for this flow. + + + Behavior settings + + + Compatibility mode + + + Increases compatibility with password managers and mobile devices. + + + Denied action + + + Will follow the ?next parameter if set, otherwise show a message + + + Will either follow the ?next parameter or redirect to the default interface + + + Will notify the user the flow isn't applicable + + + Decides the response when a policy denies access to this flow for a user. + + + Appearance settings + + + Layout + + + Background + + + Background shown during execution. + + + Clear background + + + Delete currently set background image. + + + Successfully imported flow. + + + .yaml files, which can be found on goauthentik.io and can be exported by authentik. + + + Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them. + + + Flow(s) + + + Update Flow + + + Execute + + + Export + + + Create Flow + + + Import Flow + + + Successfully cleared flow cache + + + Failed to delete flow cache + + + Clear Flow cache + + + Are you sure you want to clear the flow cache? + This will cause all flows to be re-evaluated on their next usage. + + + Stage binding(s) + + + Stage type + + + Edit Stage + + + Update Stage binding + + + These bindings control if this stage will be applied to the flow. + + + No Stages bound + + + No stages are currently bound to this flow. + + + Create Stage binding + + + Bind stage + + + Create and bind Stage + + + Bind existing stage + + + Flow Overview + + + Flow Info + + + Related actions + + + Execute flow + + + Normal + + + with current user + + + with inspector + + + Export flow + + + Stage Bindings + + + These bindings control which users can access this flow. + + + Event volume + + + Event Log + + + Event + + + Event info + + + Created + + + Successfully updated transport. + + + Successfully created transport. + + + Local (notifications will be created within authentik) + + + Webhook (generic) + + + Webhook (Slack/Discord) + + + Webhook URL + + + Webhook Mapping + + + Send once + + + Only send notification once, for example when sending a webhook into a chat channel. + + + Define how notifications are sent to users, like Email or Webhook. + + + Notification transport(s) + + + Update Notification Transport + + + Create Notification Transport + + + Successfully updated rule. + + + Successfully created rule. + + + Select the group of users which the alerts are sent to. If no group is selected the rule is disabled. + + + Transports + + + Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI. + + + Severity + + + Send notifications whenever a specific Event is created and matched by policies. + + + Sent to group + + + Notification rule(s) + + + None (rule disabled) + + + Update Notification Rule + + + Create Notification Rule + + + These bindings control upon which events this rule triggers. +Bindings to groups/users are checked against the user of the event. + + + Outpost Deployment Info + + + View deployment documentation + + + Click to copy token + + + If your authentik Instance is using a self-signed certificate, set this value. + + + If your authentik_host setting does not match the URL you want to login with, add this setting. + + + Successfully updated outpost. + + + Successfully created outpost. + + + LDAP + + + Radius + + + RAC + + + Integration + + + Selecting an integration enables the management of the outpost by authentik. + + + You can only select providers that match the type of the outpost. + + + Configuration + + + See more here: + + + Documentation + + + Last seen + + + , should be + + + Hostname + + + Not available + + + Last seen: + + + Unknown type + + + Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies. + + + Health and Version + + + Warning: authentik Domain is not configured, authentication will not work. + + + Logging in via . + + + No integration active + + + Update Outpost + + + View Deployment Info + + + Detailed health (one instance per column, data is cached so may be out of date) + + + Outpost(s) + + + Create Outpost + + + Successfully updated integration. + + + Successfully created integration. + + + Local + + + If enabled, use the local connection. Required Docker socket/Kubernetes Integration. + + + Docker URL + + + Can be in the format of 'unix://' when connecting to a local docker daemon, using 'ssh://' to connect via SSH, or 'https://:2376' when connecting to a remote system. + + + CA which the endpoint's Certificate is verified against. Can be left empty for no validation. + + + TLS Authentication Certificate/SSH Keypair + + + Certificate/Key used for authentication. Can be left empty for no authentication. + + + When connecting via SSH, this keypair is used for authentication. + + + Kubeconfig + + + Verify Kubernetes API SSL Certificate + + + New outpost integration + + + Create a new outpost integration. + + + State + + + Unhealthy + + + Outpost integration(s) + + + Successfully generated certificate-key pair. + + + Common Name + + + Subject-alt name + + + Optional, comma-separated SubjectAlt Names. + + + Validity days + + + Successfully updated certificate-key pair. + + + Successfully created certificate-key pair. + + + PEM-encoded Certificate data. + + + Optional Private Key. If this is set, you can use this keypair for encryption. + + + Certificate-Key Pairs + + + Import certificates of external providers or create certificates to sign requests with. + + + Private key available? + + + Certificate-Key Pair(s) + + + Managed by authentik + + + Managed by authentik (Discovered) + + + Yes () + + + Update Certificate-Key Pair + + + Certificate Fingerprint (SHA1) + + + Certificate Fingerprint (SHA256) + + + Certificate Subject + + + Download Certificate + + + Download Private key + + + Create Certificate-Key Pair + + + Generate + + + Generate Certificate-Key Pair + + + Successfully updated settings. + + + Avatars + + + Configure how authentik should show avatars for users. The following values can be set: + + + Disables per-user avatars and just shows a 1x1 pixel transparent picture + + + Uses gravatar with the user's email address + + + Generated avatars based on the user's name + + + Any URL: If you want to use images hosted on another server, you can set any URL. Additionally, these placeholders can be used: + + + The user's username + + + The email address, md5 hashed + + + The user's UPN, if set (otherwise an empty string) + + + An attribute path like + attributes.something.avatar, which can be used in + combination with the file field to allow users to upload custom + avatars for themselves. + + + Multiple values can be set, comma-separated, and authentik will fallback to the next mode when no avatar could be found. + + + For example, setting this to gravatar,initials will + attempt to get an avatar from Gravatar, and if the user has not + configured on there, it will fallback to a generated avatar. + + + Default user change name + + + Enable the ability for users to change their name. + + + Default user change email + + + Enable the ability for users to change their email. + + + Default user change username + + + Enable the ability for users to change their username. + + + Event retention + + + Duration after which events will be deleted from the database. + + + When using an external logging solution for archiving, this can be set to "minutes=5". + + + This setting only affects new Events, as the expiration is saved per-event. + + + Footer links + + + This option configures the footer links on the flow executor pages. It must be a valid JSON list and can be used as follows: + + + GDPR compliance + + + When enabled, all the events caused by a user will be deleted upon the user's deletion. + + + Impersonation + + + Globally enable/disable impersonation. + + + System settings + + + Save + + + Successfully updated instance. + + + Successfully created instance. + + + Disabled blueprints are never applied. + + + Local path + + + OCI Registry + + + OCI URL, in the format of oci://registry.domain.tld/path/to/manifest. + + + See more about OCI support here: + + + Blueprint + + + Configure the blueprint context, used for templating. + + + Orphaned + + + Automate and template configuration within authentik. + + + Last applied + + + Blueprint(s) + + + Update Blueprint + + + Apply + + + Create Blueprint Instance + + + Successfully updated license. + + + Successfully created license. + + + Install ID + + + License key + + + Manage enterprise licenses + + + No licenses found. + + + License(s) + + + Enterprise is in preview. + + + Get a license + + + Go to Customer Portal + + + Forecast internal users + + + Estimated user count one year from now based on current internal users and forecasted internal users. + + + Forecast external users + + + Estimated user count one year from now based on current external users and forecasted external users. + + + Cumulative license expiry + + + Internal: + + + External: + + + Update License + + + Install + + + Install License + + + WebAuthn requires this page to be accessed via HTTPS. + + + WebAuthn not supported by browser. + + + Open Wizard + + + Demo Wizard + + + Run the demo wizard + + + API request failed + + + Connection failed after attempts. + + + Re-connecting in second(s). + + + Connecting... + + + Authenticating with Apple... + + + Retry + + + Authenticating with Plex... + + + Waiting for authentication... + + + If no Plex popup opens, click the button below. + + + Open login + + + User's avatar + + + Something went wrong! Please try again later. + + + Request ID + + + You may close this page now. + + + You're about to be redirect to the following URL. + + + Follow redirect + + + Request has been denied. + + + Not you? + + + Need an account? + + + Sign up. + + + Forgot username or password? + + + Select one of the sources below to login. + + + Or + + + Use a security key + + + Login to continue to . + + + Please enter your password + + + Forgot password? + + + Application requires following permissions: + + + Application already has access to the following permissions: + + + Application requires following new permissions: + + + Check your Inbox for a verification email. + + + Send Email again. + + + Successfully copied TOTP Config. + + + Copy + + + Code + + + Please enter your TOTP Code + + + Duo activation QR code + + + Alternatively, if your current device has Duo installed, click on this link: + + + Duo activation + + + Check status + + + Make sure to keep these tokens in a safe place. + + + Phone number + + + Please enter your Phone number. + + + Please enter the code you received via SMS + + + A code has been sent to you via SMS. + + + Open your two-factor authenticator app to view your authentication code. + + + Static token + + + Authentication code + + + Please enter your code + + + Return to device picker + + + Sending Duo push notification + + + Assertions is empty + + + Error when creating credential: + + + Error when validating assertion on server: + + + Retry authentication + + + Duo push-notifications + + + Receive a push notification on your device. + + + Authenticator + + + Use a security key to prove your identity. + + + Traditional authenticator + + + Use a code-based authenticator. + + + Recovery keys + + + In case you can't access any other method. + + + SMS + + + Tokens sent via SMS. + + + Select an authentication method. + + + Stay signed in? + + + Select Yes to reduce the number of times you're asked to sign in. + + + Enter the code shown on your device. + + + Please enter your Code + + + You've successfully authenticated your device. + + + Flow inspector + + + Next stage + + + Stage name + + + Stage kind + + + Stage object + + + This flow is completed. + + + Plan history + + + Current plan context + + + Session ID + + + Powered by authentik + + + Background image + + + Error creating credential: + + + Server validation of credential failed: + + + Register device + + + Select endpoint to connect to + + + Unread notifications + + + Sign out + + + Admin interface + + + Stop impersonation + + + Avatar image + + + Less details + + + More details + + + Refer to documentation + + + No Applications available. + + + Either no applications are defined, or you don’t have access to any. + + + My Applications + + + My applications + + + Change your password + + + Change password + + + + + + Delete account + + + Successfully updated details + + + Open settings + + + No settings flow configured. + + + Update details + + + Successfully updated device. + + + Enroll + + + Update Device + + + Successfully disconnected source + + + Failed to disconnected source: + + + Disconnect + + + Connect + + + Error: unsupported source settings: + + + Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials. + + + No services available. + + + Create App password + + + User details + + + Consent + + + MFA Devices + + + Connected services + + + + diff --git a/web/xliff/zh-Hans.xlf b/web/xliff/zh-Hans.xlf index 69eb76c0b..3d6f02923 100644 --- a/web/xliff/zh-Hans.xlf +++ b/web/xliff/zh-Hans.xlf @@ -8145,6 +8145,22 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. 设置会话在被断开连接并需要重新授权之前持续的时间。 + + Provider require enterprise. + 提供程序需要企业版。 + + + Learn more + 了解更多 + + + Maximum concurrent connections + 最大并发连接数 + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + 允许到此端点的最大并发连接数。可以设置为 -1 以禁用限制。 + Brand @@ -8175,6 +8191,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/zh-Hant.xlf b/web/xliff/zh-Hant.xlf index 8ed4da7a0..d2c092bf0 100644 --- a/web/xliff/zh-Hant.xlf +++ b/web/xliff/zh-Hant.xlf @@ -6149,6 +6149,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -6179,6 +6191,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/web/xliff/zh_CN.xlf b/web/xliff/zh_CN.xlf index ea9939a67..9f27a492e 100644 --- a/web/xliff/zh_CN.xlf +++ b/web/xliff/zh_CN.xlf @@ -8203,6 +8203,22 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. 设置会话在被断开连接并需要重新授权之前持续的时间。 + + + Provider require enterprise. + 提供程序需要企业版。 + + + Learn more + 了解更多 + + + Maximum concurrent connections + 最大并发连接数 + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + 允许到此端点的最大并发连接数。可以设置为 -1 以禁用限制。 diff --git a/web/xliff/zh_TW.xlf b/web/xliff/zh_TW.xlf index 192a55da5..7ee0e7217 100644 --- a/web/xliff/zh_TW.xlf +++ b/web/xliff/zh_TW.xlf @@ -8025,6 +8025,18 @@ Bindings to groups/users are checked against the user of the event. Determines how long a session lasts before being disconnected and requiring re-authorization. + + Provider require enterprise. + + + Learn more + + + Maximum concurrent connections + + + Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit. + Brand @@ -8055,6 +8067,12 @@ Bindings to groups/users are checked against the user of the event. To let a user directly reset a their password, configure a recovery flow on the currently active brand. + + Korean + + + Dutch + The current brand must have a recovery flow configured to use a recovery link diff --git a/website/docs/flow/index.md b/website/docs/flow/index.md index d13ea1e5e..0fd0c510c 100644 --- a/website/docs/flow/index.md +++ b/website/docs/flow/index.md @@ -65,4 +65,6 @@ This designates a flow for general setup. This designation doesn't have any cons Flows can be imported and exported to share with other people, the community and for troubleshooting. Flows can be imported to apply new functionality and apply existing workflows. +Download our [Example flows](./examples/flows.md) and then import them into your authentik instance. + Starting with authentik 2022.8, flows will be exported as YAML, but JSON-based flows can still be imported. diff --git a/website/docs/providers/oauth2/index.md b/website/docs/providers/oauth2/index.md index 0eba63765..90ce75778 100644 --- a/website/docs/providers/oauth2/index.md +++ b/website/docs/providers/oauth2/index.md @@ -35,12 +35,20 @@ To access the user's email address, a scope of `user:email` is required. To acce ### `authorization_code`: -This grant is used to convert an authorization code to a refresh token. The authorization code is retrieved through the Authorization flow, and can only be used once, and expires quickly. +This grant is used to convert an authorization code to an access token (and optionally refresh token). The authorization code is retrieved through the Authorization flow, and can only be used once, and expires quickly. + +:::info +Starting with authentik 2024.1, applications only receive an access token. To receive a refresh token, applications must be allowed to request the `offline_access` scope in authentik and also be configured to request the scope. +::: ### `refresh_token`: Refresh tokens can be used as long-lived tokens to access user data, and further renew the refresh token down the road. +:::info +Starting with authentik 2024.1, this grant requires the `offline_access` scope. +::: + ### `client_credentials`: See [Machine-to-machine authentication](./client_credentials) diff --git a/website/docs/releases/2024/v2024.1.md b/website/docs/releases/2024/v2024.1.md index 54c488a32..8168c31ba 100644 --- a/website/docs/releases/2024/v2024.1.md +++ b/website/docs/releases/2024/v2024.1.md @@ -17,6 +17,12 @@ slug: "/releases/2024.1" - `authentik_outpost_radius_requests_rejected` -> `authentik_outpost_radius_requests_rejected_total` - `authentik_main_requests` -> `authentik_main_request_duration_seconds` +- Required `offline_access` scope for Refresh tokens + + The OAuth2 provider ships with a new default scope called `offline_access`, which must be requested by applications that need a refresh token. Previously, authentik would always issue a refresh token for the _Authorization code_ and _Device code_ OAuth grants. + + Applications which require will need their configuration update to include the `offline_access` scope mapping. + ## New features - "Pretend user exists" option for Identification stage diff --git a/website/integrations/services/jellyfin/index.md b/website/integrations/services/jellyfin/index.md index 5de18d3b4..ef66efd27 100644 --- a/website/integrations/services/jellyfin/index.md +++ b/website/integrations/services/jellyfin/index.md @@ -15,7 +15,7 @@ Jellyfin does not have any native external authentication support as of the writ ::: :::note -Currently there are two plugins for Jelyfin that provide external authenticaion, an OIDC plugin and an LDAP plugin. This guide focuses on the use of the LDAP plugin. +Currently, there are two plugins for Jellyfin that provide external authentication, an OIDC plugin and an LDAP plugin. This guide focuses on the use of the LDAP plugin. ::: :::caution @@ -34,49 +34,49 @@ The following placeholders will be used: ## Jellyfin configuration -1. If you don't have one already create an LDAP bind user before starting these steps. +1. If you don't have one already, create an LDAP bind user before starting these steps. - Ideally, this user doesn't have any permissions other than the ability to view other users. However, some functions do require an account with permissions. - This user must be part of the group that is specified in the "Search group" in the LDAP outpost. 2. Navigate to your Jellyfin installation and log in with the admin account or currently configured local admin. 3. Open the administrator dashboard and go to the "Plugins" section. 4. Click "Catalog" at the top of the page, and locate the "LDAP Authentication Plugin" 5. Install the plugin. You may need to restart Jellyfin to finish installation. -6. Once finished navigate back to the plugins section of the admin dashboard, click the 3 dots on the "LDAP-Auth Plugin" card, and click settings. +6. Once finished, navigate back to the plugins section of the admin dashboard, click the 3 dots on the "LDAP-Auth Plugin" card, and click settings. 7. Configure the LDAP Settings as follows: - `LDAP Server`: `ldap.company.com` - `LDAP Port`: 636 - `Secure LDAP`: **Checked** - `StartTLS`: Unchecked - `Skip SSL/TLS Verification`: - - If using a certificate issued by a certificate authority Jellyfin trusts, leave this unchecked. - - If you're using a self signed certificate, check this box. + - If using a certificate issued by a certificate authority, Jellyfin trusts, leave this unchecked. + - If you're using a self-signed certificate, check this box. - `Allow password change`: Unchecked - - Since authentik already has a frontend for password resets, its not necessary to include this in Jellyfin, especially since it requires bind user to have privileges. + - Since authentik already has a frontend for password resets, it's not necessary to include this in Jellyfin, especially since it requires bind user to have privileges. - `Password Reset URL`: Empty - - `LDAP Bind User`: Set this to a the user you want to bind to in authentik. By default the path will be `ou=users,dc=company,dc=com` so the LDAP Bind user will be `cn=ldap_bind_user,ou=users,dc=company,dc=com`. + - `LDAP Bind User`: Set this to a user you want to bind to in authentik. By default, the path will be `ou=users,dc=company,dc=com` so the LDAP Bind user will be `cn=ldap_bind_user,ou=users,dc=company,dc=com`. - `LDAP Bind User Password`: The Password of the user. If using a Service account, this is the token. - - `LDAP Base DN for Searches`: the base DN for LDAP queries. To query all users set this to `dc=company,dc=com`. + - `LDAP Base DN for Searches`: the base DN for LDAP queries. To query all users, set this to `dc=company,dc=com`. - You can specify an OU if you divide your users up into different OUs and only want to query a specific OU. -At this point click `Save and Test LDAP Server Settings`. If the settings are correct you will see: +At this point, click `Save and Test LDAP Server Settings`. If the settings are correct, you will see: `Connect(Success); Bind(Success); Base Search (Found XY Entities)` - `LDAP User Filter`: This is used to a user filter on what users are allowed to login. **This must be set** - To allow all users: `(objectClass=user)` - To only allow users in a specific group: `(memberOf=cn=jellyfin_users,ou=groups,dc=company,dc=com)` - Good Docs on LDAP Filters: [atlassian.com](https://confluence.atlassian.com/kb/how-to-write-ldap-search-filters-792496933.html) -- `LDAP Admin Base DN`: All of the users in this DN are automatically set as admins. - - This can be left blank. Admins can be set manually outside of this filter +- `LDAP Admin Base DN`: All the users in this DN are automatically set as admins. + - This can be left blank. Admins can be set manually outside this filter - `LDAP Admin Filter`: Similar to the user filter, but every matched user is set as admin. - - This can be left blank. Admins can be set manually outside of this filter + - This can be left blank. Admins can be set manually outside this filter -At this point click `Save and Test LDAP Filter Settings`. If the settings are correct you will see: +At this point, click `Save and Test LDAP Filter Settings`. If the settings are correct, you will see: `Found X user(s), Y admin(s)` - `LDAP Attributes`: `uid, cn, mail, displayName` - `Enable case Insensitive Username`: **Checked** -At this point, enter in a username and click "Save Search Attribute Settings and Query User". If the settings are correct you will see: +At this point, enter a username and click "Save Search Attribute Settings and Query User". If the settings are correct, you will see: `Found User: cn=test,ou=users,dc=company,dc=com` - `Enabled User Creation`: **Checked** diff --git a/website/package-lock.json b/website/package-lock.json index 743f24714..488dda4d8 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -18,7 +18,7 @@ "@mdx-js/react": "^3.0.0", "clsx": "^2.1.0", "disqus-react": "^1.1.5", - "postcss": "^8.4.32", + "postcss": "^8.4.33", "prism-react-renderer": "^2.3.1", "rapidoc": "^9.3.4", "react": "^18.2.0", @@ -33,7 +33,7 @@ "@docusaurus/module-type-aliases": "3.0.1", "@docusaurus/tsconfig": "3.0.1", "@docusaurus/types": "3.0.1", - "@types/react": "^18.2.46", + "@types/react": "^18.2.47", "prettier": "3.1.1", "typescript": "~5.3.3" }, @@ -4373,9 +4373,9 @@ "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" }, "node_modules/@types/react": { - "version": "18.2.46", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.46.tgz", - "integrity": "sha512-nNCvVBcZlvX4NU1nRRNV/mFl1nNRuTuslAJglQsq+8ldXe5Xv0Wd2f7WTE3jOxhLH2BFfiZGC6GCp+kHQbgG+w==", + "version": "18.2.47", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.47.tgz", + "integrity": "sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -7898,9 +7898,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -13137,9 +13137,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "funding": [ { "type": "opencollective", diff --git a/website/package.json b/website/package.json index 7773daee1..37ba114a8 100644 --- a/website/package.json +++ b/website/package.json @@ -25,7 +25,7 @@ "@mdx-js/react": "^3.0.0", "clsx": "^2.1.0", "disqus-react": "^1.1.5", - "postcss": "^8.4.32", + "postcss": "^8.4.33", "prism-react-renderer": "^2.3.1", "rapidoc": "^9.3.4", "react-before-after-slider-component": "^1.1.8", @@ -52,7 +52,7 @@ "@docusaurus/module-type-aliases": "3.0.1", "@docusaurus/tsconfig": "3.0.1", "@docusaurus/types": "3.0.1", - "@types/react": "^18.2.46", + "@types/react": "^18.2.47", "prettier": "3.1.1", "typescript": "~5.3.3" },