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

This commit is contained in:
Marc 'risson' Schmitt 2024-01-09 16:38:09 +01:00
commit 9c7600e1f2
No known key found for this signature in database
GPG Key ID: 9C3FA22FABF1AA8D
104 changed files with 7649 additions and 1148 deletions

View File

@ -19,6 +19,7 @@ from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer, TypeCreateSerializer from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer, TypeCreateSerializer
from authentik.core.expression.evaluator import PropertyMappingEvaluator from authentik.core.expression.evaluator import PropertyMappingEvaluator
from authentik.core.models import PropertyMapping from authentik.core.models import PropertyMapping
from authentik.enterprise.apps import EnterpriseConfig
from authentik.events.utils import sanitize_item from authentik.events.utils import sanitize_item
from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.reflection import all_subclasses
from authentik.policies.api.exec import PolicyTestSerializer from authentik.policies.api.exec import PolicyTestSerializer
@ -95,6 +96,7 @@ class PropertyMappingViewSet(
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass().component, "component": subclass().component,
"model_name": subclass._meta.model_name, "model_name": subclass._meta.model_name,
"requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig),
} }
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -16,6 +16,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.enterprise.apps import EnterpriseConfig
from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.reflection import all_subclasses
@ -113,6 +114,7 @@ class ProviderViewSet(
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass().component, "component": subclass().component,
"model_name": subclass._meta.model_name, "model_name": subclass._meta.model_name,
"requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig),
} }
) )
data.append( data.append(

View File

@ -5,7 +5,7 @@ from django.db.models import Model
from drf_spectacular.extensions import OpenApiSerializerFieldExtension from drf_spectacular.extensions import OpenApiSerializerFieldExtension
from drf_spectacular.plumbing import build_basic_type from drf_spectacular.plumbing import build_basic_type
from drf_spectacular.types import OpenApiTypes 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 from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError
@ -74,6 +74,7 @@ class TypeCreateSerializer(PassiveSerializer):
description = CharField(required=True) description = CharField(required=True)
component = CharField(required=True) component = CharField(required=True)
model_name = CharField(required=True) model_name = CharField(required=True)
requires_enterprise = BooleanField(default=False)
class CacheSerializer(PassiveSerializer): class CacheSerializer(PassiveSerializer):

View File

@ -2,9 +2,11 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, inline_serializer from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import BooleanField, CharField, DateTimeField, IntegerField from rest_framework.fields import BooleanField, CharField, DateTimeField, IntegerField
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request 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 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): class LicenseSerializer(ModelSerializer):
"""License Serializer""" """License Serializer"""

View File

@ -2,7 +2,11 @@
from authentik.blueprints.apps import ManagedAppConfig 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""" """Enterprise app config"""
name = "authentik.enterprise" name = "authentik.enterprise"

View File

@ -15,6 +15,7 @@ from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import Provider 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.api.providers import RACProviderSerializer
from authentik.enterprise.providers.rac.models import Endpoint from authentik.enterprise.providers.rac.models import Endpoint
from authentik.policies.engine import PolicyEngine 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}" return f"goauthentik.io/providers/rac/endpoint_access/{user_pk}"
class EndpointSerializer(ModelSerializer): class EndpointSerializer(EnterpriseRequiredMixin, ModelSerializer):
"""Endpoint Serializer""" """Endpoint Serializer"""
provider_obj = RACProviderSerializer(source="provider", read_only=True) provider_obj = RACProviderSerializer(source="provider", read_only=True)
@ -59,6 +60,7 @@ class EndpointSerializer(ModelSerializer):
"property_mappings", "property_mappings",
"auth_mode", "auth_mode",
"launch_url", "launch_url",
"maximum_connections",
] ]

View File

@ -5,10 +5,11 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.api.propertymappings import PropertyMappingSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField from authentik.core.api.utils import JSONDictField
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.providers.rac.models import RACPropertyMapping from authentik.enterprise.providers.rac.models import RACPropertyMapping
class RACPropertyMappingSerializer(PropertyMappingSerializer): class RACPropertyMappingSerializer(EnterpriseRequiredMixin, PropertyMappingSerializer):
"""RACPropertyMapping Serializer""" """RACPropertyMapping Serializer"""
static_settings = JSONDictField() static_settings = JSONDictField()

View File

@ -4,10 +4,11 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.providers.rac.models import RACProvider from authentik.enterprise.providers.rac.models import RACProvider
class RACProviderSerializer(ProviderSerializer): class RACProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer):
"""RACProvider Serializer""" """RACProvider Serializer"""
outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all") outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all")

View File

@ -1,8 +1,8 @@
"""RAC app config""" """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""" """authentik enterprise rac app config"""
name = "authentik.enterprise.providers.rac" name = "authentik.enterprise.providers.rac"

View File

@ -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),
),
]

View File

@ -35,7 +35,7 @@ class AuthenticationMode(models.TextChoices):
class RACProvider(Provider): class RACProvider(Provider):
"""Remotely access computers/servers""" """Remotely access computers/servers via RDP/SSH/VNC."""
settings = models.JSONField(default=dict) settings = models.JSONField(default=dict)
auth_mode = models.TextField( auth_mode = models.TextField(
@ -81,6 +81,7 @@ class Endpoint(SerializerModel, PolicyBindingModel):
settings = models.JSONField(default=dict) settings = models.JSONField(default=dict)
auth_mode = models.TextField(choices=AuthenticationMode.choices) auth_mode = models.TextField(choices=AuthenticationMode.choices)
provider = models.ForeignKey("RACProvider", on_delete=models.CASCADE) provider = models.ForeignKey("RACProvider", on_delete=models.CASCADE)
maximum_connections = models.IntegerField(default=1)
property_mappings = models.ManyToManyField( property_mappings = models.ManyToManyField(
"authentik_core.PropertyMapping", default=None, blank=True "authentik_core.PropertyMapping", default=None, blank=True

View File

@ -81,6 +81,7 @@ class TestEndpointsAPI(APITestCase):
}, },
"protocol": "rdp", "protocol": "rdp",
"host": self.allowed.host, "host": self.allowed.host,
"maximum_connections": 1,
"settings": {}, "settings": {},
"property_mappings": [], "property_mappings": [],
"auth_mode": "", "auth_mode": "",
@ -131,6 +132,7 @@ class TestEndpointsAPI(APITestCase):
}, },
"protocol": "rdp", "protocol": "rdp",
"host": self.allowed.host, "host": self.allowed.host,
"maximum_connections": 1,
"settings": {}, "settings": {},
"property_mappings": [], "property_mappings": [],
"auth_mode": "", "auth_mode": "",
@ -158,6 +160,7 @@ class TestEndpointsAPI(APITestCase):
}, },
"protocol": "rdp", "protocol": "rdp",
"host": self.denied.host, "host": self.denied.host,
"maximum_connections": 1,
"settings": {}, "settings": {},
"property_mappings": [], "property_mappings": [],
"auth_mode": "", "auth_mode": "",

View File

@ -5,11 +5,13 @@ from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _
from authentik.core.models import Application, AuthenticatedSession from authentik.core.models import Application, AuthenticatedSession
from authentik.core.views.interface import InterfaceView from authentik.core.views.interface import InterfaceView
from authentik.enterprise.policy import EnterprisePolicyAccessView from authentik.enterprise.policy import EnterprisePolicyAccessView
from authentik.enterprise.providers.rac.models import ConnectionToken, Endpoint, RACProvider 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.challenge import RedirectChallenge
from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage from authentik.flows.models import in_memory_stage
@ -43,6 +45,7 @@ class RACStartView(EnterprisePolicyAccessView):
plan.insert_stage( plan.insert_stage(
in_memory_stage( in_memory_stage(
RACFinalStage, RACFinalStage,
application=self.application,
endpoint=self.endpoint, endpoint=self.endpoint,
provider=self.provider, provider=self.provider,
) )
@ -77,29 +80,51 @@ class RACInterface(InterfaceView):
class RACFinalStage(RedirectStage): class RACFinalStage(RedirectStage):
"""RAC Connection final stage, set the connection token in the stage""" """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: def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
endpoint: Endpoint = self.executor.current_stage.endpoint self.endpoint = self.executor.current_stage.endpoint
engine = PolicyEngine(endpoint, self.request.user, self.request) 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.use_cache = False
engine.build() engine.build()
passing = engine.result passing = engine.result
if not passing.passing: if not passing.passing:
return self.executor.stage_invalid(", ".join(passing.messages)) 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) return super().dispatch(request, *args, **kwargs)
def get_challenge(self, *args, **kwargs) -> RedirectChallenge: 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( token = ConnectionToken.objects.create(
provider=provider, provider=self.provider,
endpoint=endpoint, endpoint=self.endpoint,
settings=self.executor.plan.context.get("connection_settings", {}), settings=self.executor.plan.context.get("connection_settings", {}),
session=AuthenticatedSession.objects.filter( session=AuthenticatedSession.objects.filter(
session_key=self.request.session.session_key session_key=self.request.session.session_key
).first(), ).first(),
expires=now() + timedelta_from_string(provider.connection_expiry), expires=now() + timedelta_from_string(self.provider.connection_expiry),
expiring=True, 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( setattr(
self.executor.current_stage, self.executor.current_stage,
"destination", "destination",

View File

@ -20,6 +20,7 @@ from authentik.core.models import (
User, User,
UserSourceConnection, UserSourceConnection,
) )
from authentik.enterprise.providers.rac.models import ConnectionToken
from authentik.events.models import Event, EventAction, Notification from authentik.events.models import Event, EventAction, Notification
from authentik.events.utils import model_to_dict from authentik.events.utils import model_to_dict
from authentik.flows.models import FlowToken, Stage from authentik.flows.models import FlowToken, Stage
@ -54,6 +55,7 @@ IGNORED_MODELS = (
SCIMUser, SCIMUser,
SCIMGroup, SCIMGroup,
Reputation, Reputation,
ConnectionToken,
) )

View File

@ -7,8 +7,8 @@ GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"
GRANT_TYPE_PASSWORD = "password" # nosec GRANT_TYPE_PASSWORD = "password" # nosec
GRANT_TYPE_DEVICE_CODE = "urn:ietf:params:oauth:grant-type:device_code" GRANT_TYPE_DEVICE_CODE = "urn:ietf:params:oauth:grant-type:device_code"
CLIENT_ASSERTION_TYPE = "client_assertion_type"
CLIENT_ASSERTION = "client_assertion" CLIENT_ASSERTION = "client_assertion"
CLIENT_ASSERTION_TYPE = "client_assertion_type"
CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
PROMPT_NONE = "none" PROMPT_NONE = "none"
@ -18,9 +18,9 @@ PROMPT_LOGIN = "login"
SCOPE_OPENID = "openid" SCOPE_OPENID = "openid"
SCOPE_OPENID_PROFILE = "profile" SCOPE_OPENID_PROFILE = "profile"
SCOPE_OPENID_EMAIL = "email" SCOPE_OPENID_EMAIL = "email"
SCOPE_OFFLINE_ACCESS = "offline_access"
# https://www.iana.org/assignments/oauth-parameters/\ # https://www.iana.org/assignments/oauth-parameters/auth-parameters.xhtml#pkce-code-challenge-method
# oauth-parameters.xhtml#pkce-code-challenge-method
PKCE_METHOD_PLAIN = "plain" PKCE_METHOD_PLAIN = "plain"
PKCE_METHOD_S256 = "S256" PKCE_METHOD_S256 = "S256"
@ -36,6 +36,12 @@ SCOPE_GITHUB_USER_READ = "read:user"
SCOPE_GITHUB_USER_EMAIL = "user:email" SCOPE_GITHUB_USER_EMAIL = "user:email"
# Read info about teams # Read info about teams
SCOPE_GITHUB_ORG_READ = "read:org" 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" ACR_AUTHENTIK_DEFAULT = "goauthentik.io/providers/oauth2/default"

View File

@ -127,7 +127,7 @@ class AuthorizeError(OAuth2Error):
"account_selection_required": ( "account_selection_required": (
"The End-User is required to select a session at the Authorization Server" "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": ( "invalid_request_uri": (
"The request_uri in the Authorization Request returns an error or contains invalid data" "The request_uri in the Authorization Request returns an error or contains invalid data"
), ),

View File

@ -5,6 +5,7 @@ from django.test import RequestFactory
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@ -18,6 +19,7 @@ from authentik.providers.oauth2.models import (
AuthorizationCode, AuthorizationCode,
GrantTypes, GrantTypes,
OAuth2Provider, OAuth2Provider,
ScopeMapping,
) )
from authentik.providers.oauth2.tests.utils import OAuthTestCase from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.views.authorize import OAuthAuthorizationParams from authentik.providers.oauth2.views.authorize import OAuthAuthorizationParams
@ -172,14 +174,24 @@ class TestAuthorize(OAuthTestCase):
) )
OAuthAuthorizationParams.from_request(request) OAuthAuthorizationParams.from_request(request)
@apply_blueprint("system/providers-oauth2.yaml")
def test_response_type(self): def test_response_type(self):
"""test response_type""" """test response_type"""
OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name=generate_id(), name=generate_id(),
client_id="test", client_id="test",
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid/Foo", 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( request = self.factory.get(
"/", "/",
data={ data={
@ -292,6 +304,7 @@ class TestAuthorize(OAuthTestCase):
delta=5, delta=5,
) )
@apply_blueprint("system/providers-oauth2.yaml")
def test_full_implicit(self): def test_full_implicit(self):
"""Test full authorization""" """Test full authorization"""
flow = create_test_flow() flow = create_test_flow()
@ -302,6 +315,15 @@ class TestAuthorize(OAuthTestCase):
redirect_uris="http://localhost", redirect_uris="http://localhost",
signing_key=self.keypair, 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) Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id() state = generate_id()
user = create_test_admin_user() user = create_test_admin_user()
@ -409,6 +431,7 @@ class TestAuthorize(OAuthTestCase):
delta=5, delta=5,
) )
@apply_blueprint("system/providers-oauth2.yaml")
def test_full_form_post_id_token(self): def test_full_form_post_id_token(self):
"""Test full authorization (form_post response)""" """Test full authorization (form_post response)"""
flow = create_test_flow() flow = create_test_flow()
@ -419,6 +442,15 @@ class TestAuthorize(OAuthTestCase):
redirect_uris="http://localhost", redirect_uris="http://localhost",
signing_key=self.keypair, 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) app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
state = generate_id() state = generate_id()
user = create_test_admin_user() user = create_test_admin_user()
@ -440,6 +472,7 @@ class TestAuthorize(OAuthTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
) )
token: AccessToken = AccessToken.objects.filter(user=user).first() token: AccessToken = AccessToken.objects.filter(user=user).first()
self.assertIsNotNone(token)
self.assertJSONEqual( self.assertJSONEqual(
response.content.decode(), response.content.decode(),
{ {

View File

@ -6,6 +6,7 @@ from django.test import RequestFactory
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@ -21,6 +22,7 @@ from authentik.providers.oauth2.models import (
AuthorizationCode, AuthorizationCode,
OAuth2Provider, OAuth2Provider,
RefreshToken, RefreshToken,
ScopeMapping,
) )
from authentik.providers.oauth2.tests.utils import OAuthTestCase from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.views.token import TokenParams from authentik.providers.oauth2.views.token import TokenParams
@ -136,21 +138,20 @@ class TestToken(OAuthTestCase):
HTTP_AUTHORIZATION=f"Basic {header}", HTTP_AUTHORIZATION=f"Basic {header}",
) )
access: AccessToken = AccessToken.objects.filter(user=user, provider=provider).first() access: AccessToken = AccessToken.objects.filter(user=user, provider=provider).first()
refresh: RefreshToken = RefreshToken.objects.filter(user=user, provider=provider).first()
self.assertJSONEqual( self.assertJSONEqual(
response.content.decode(), response.content.decode(),
{ {
"access_token": access.token, "access_token": access.token,
"refresh_token": refresh.token,
"token_type": TOKEN_TYPE, "token_type": TOKEN_TYPE,
"expires_in": 3600, "expires_in": 3600,
"id_token": provider.encode( "id_token": provider.encode(
refresh.id_token.to_dict(), access.id_token.to_dict(),
), ),
}, },
) )
self.validate_jwt(access, provider) self.validate_jwt(access, provider)
@apply_blueprint("system/providers-oauth2.yaml")
def test_refresh_token_view(self): def test_refresh_token_view(self):
"""test request param""" """test request param"""
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
@ -159,6 +160,16 @@ class TestToken(OAuthTestCase):
redirect_uris="http://local.invalid", redirect_uris="http://local.invalid",
signing_key=self.keypair, 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 # Needs to be assigned to an application for iss to be set
self.app.provider = provider self.app.provider = provider
self.app.save() self.app.save()
@ -170,6 +181,7 @@ class TestToken(OAuthTestCase):
token=generate_id(), token=generate_id(),
_id_token=dumps({}), _id_token=dumps({}),
auth_time=timezone.now(), auth_time=timezone.now(),
_scope="offline_access",
) )
response = self.client.post( response = self.client.post(
reverse("authentik_providers_oauth2:token"), reverse("authentik_providers_oauth2:token"),
@ -201,6 +213,7 @@ class TestToken(OAuthTestCase):
) )
self.validate_jwt(access, provider) self.validate_jwt(access, provider)
@apply_blueprint("system/providers-oauth2.yaml")
def test_refresh_token_view_invalid_origin(self): def test_refresh_token_view_invalid_origin(self):
"""test request param""" """test request param"""
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
@ -209,6 +222,16 @@ class TestToken(OAuthTestCase):
redirect_uris="http://local.invalid", redirect_uris="http://local.invalid",
signing_key=self.keypair, 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() header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user() user = create_test_admin_user()
token: RefreshToken = RefreshToken.objects.create( token: RefreshToken = RefreshToken.objects.create(
@ -217,6 +240,7 @@ class TestToken(OAuthTestCase):
token=generate_id(), token=generate_id(),
_id_token=dumps({}), _id_token=dumps({}),
auth_time=timezone.now(), auth_time=timezone.now(),
_scope="offline_access",
) )
response = self.client.post( response = self.client.post(
reverse("authentik_providers_oauth2:token"), reverse("authentik_providers_oauth2:token"),
@ -247,6 +271,7 @@ class TestToken(OAuthTestCase):
}, },
) )
@apply_blueprint("system/providers-oauth2.yaml")
def test_refresh_token_revoke(self): def test_refresh_token_revoke(self):
"""test request param""" """test request param"""
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
@ -255,6 +280,16 @@ class TestToken(OAuthTestCase):
redirect_uris="http://testserver", redirect_uris="http://testserver",
signing_key=self.keypair, 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 # Needs to be assigned to an application for iss to be set
self.app.provider = provider self.app.provider = provider
self.app.save() self.app.save()
@ -266,6 +301,7 @@ class TestToken(OAuthTestCase):
token=generate_id(), token=generate_id(),
_id_token=dumps({}), _id_token=dumps({}),
auth_time=timezone.now(), auth_time=timezone.now(),
_scope="offline_access",
) )
# Create initial refresh token # Create initial refresh token
response = self.client.post( response = self.client.post(

View File

@ -10,7 +10,7 @@ from authentik.providers.oauth2.views.token import TokenView
github_urlpatterns = [ github_urlpatterns = [
path( path(
"login/oauth/authorize", "login/oauth/authorize",
AuthorizationFlowInitView.as_view(), AuthorizationFlowInitView.as_view(github_compat=True),
name="github-authorize", name="github-authorize",
), ),
path( path(

View File

@ -1,5 +1,5 @@
"""authentik OAuth2 Authorization views""" """authentik OAuth2 Authorization views"""
from dataclasses import dataclass, field from dataclasses import InitVar, dataclass, field
from datetime import timedelta from datetime import timedelta
from hashlib import sha256 from hashlib import sha256
from json import dumps from json import dumps
@ -41,6 +41,8 @@ from authentik.providers.oauth2.constants import (
PROMPT_CONSENT, PROMPT_CONSENT,
PROMPT_LOGIN, PROMPT_LOGIN,
PROMPT_NONE, PROMPT_NONE,
SCOPE_GITHUB,
SCOPE_OFFLINE_ACCESS,
SCOPE_OPENID, SCOPE_OPENID,
TOKEN_TYPE, TOKEN_TYPE,
) )
@ -66,7 +68,6 @@ from authentik.stages.consent.models import ConsentMode, ConsentStage
from authentik.stages.consent.stage import ( from authentik.stages.consent.stage import (
PLAN_CONTEXT_CONSENT_HEADER, PLAN_CONTEXT_CONSENT_HEADER,
PLAN_CONTEXT_CONSENT_PERMISSIONS, PLAN_CONTEXT_CONSENT_PERMISSIONS,
ConsentStageView,
) )
LOGGER = get_logger() LOGGER = get_logger()
@ -86,7 +87,7 @@ class OAuthAuthorizationParams:
redirect_uri: str redirect_uri: str
response_type: str response_type: str
response_mode: Optional[str] response_mode: Optional[str]
scope: list[str] scope: set[str]
state: str state: str
nonce: Optional[str] nonce: Optional[str]
prompt: set[str] prompt: set[str]
@ -101,8 +102,10 @@ class OAuthAuthorizationParams:
code_challenge: Optional[str] = None code_challenge: Optional[str] = None
code_challenge_method: Optional[str] = None code_challenge_method: Optional[str] = None
github_compat: InitVar[bool] = False
@staticmethod @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 Get all the params used by the Authorization Code Flow
(and also for the Implicit and Hybrid). (and also for the Implicit and Hybrid).
@ -154,7 +157,7 @@ class OAuthAuthorizationParams:
response_type=response_type, response_type=response_type,
response_mode=response_mode, response_mode=response_mode,
grant_type=grant_type, grant_type=grant_type,
scope=query_dict.get("scope", "").split(), scope=set(query_dict.get("scope", "").split()),
state=state, state=state,
nonce=query_dict.get("nonce"), nonce=query_dict.get("nonce"),
prompt=ALLOWED_PROMPT_PARAMS.intersection(set(query_dict.get("prompt", "").split())), 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, max_age=int(max_age) if max_age else None,
code_challenge=query_dict.get("code_challenge"), code_challenge=query_dict.get("code_challenge"),
code_challenge_method=query_dict.get("code_challenge_method", "plain"), 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( self.provider: OAuth2Provider = OAuth2Provider.objects.filter(
client_id=self.client_id client_id=self.client_id
).first() ).first()
@ -172,7 +176,7 @@ class OAuthAuthorizationParams:
LOGGER.warning("Invalid client identifier", client_id=self.client_id) LOGGER.warning("Invalid client identifier", client_id=self.client_id)
raise ClientIdError(client_id=self.client_id) raise ClientIdError(client_id=self.client_id)
self.check_redirect_uri() self.check_redirect_uri()
self.check_scope() self.check_scope(github_compat)
self.check_nonce() self.check_nonce()
self.check_code_challenge() self.check_code_challenge()
@ -199,8 +203,8 @@ class OAuthAuthorizationParams:
if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls): if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
LOGGER.warning( LOGGER.warning(
"Invalid redirect uri (regex comparison)", "Invalid redirect uri (regex comparison)",
redirect_uri=self.redirect_uri, redirect_uri_given=self.redirect_uri,
expected=allowed_redirect_urls, redirect_uri_expected=allowed_redirect_urls,
) )
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
except RegexError as exc: except RegexError as exc:
@ -208,8 +212,8 @@ class OAuthAuthorizationParams:
if not any(x == self.redirect_uri for x in allowed_redirect_urls): if not any(x == self.redirect_uri for x in allowed_redirect_urls):
LOGGER.warning( LOGGER.warning(
"Invalid redirect uri (strict comparison)", "Invalid redirect uri (strict comparison)",
redirect_uri=self.redirect_uri, redirect_uri_given=self.redirect_uri,
expected=allowed_redirect_urls, redirect_uri_expected=allowed_redirect_urls,
) )
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
if self.request: if self.request:
@ -217,24 +221,50 @@ class OAuthAuthorizationParams:
self.redirect_uri, "request_not_supported", self.grant_type, self.state 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""" """Ensure openid scope is set in Hybrid flows, or when requesting an id_token"""
if len(self.scope) == 0:
default_scope_names = set( default_scope_names = set(
ScopeMapping.objects.filter(provider__in=[self.provider]).values_list( ScopeMapping.objects.filter(provider__in=[self.provider]).values_list(
"scope_name", flat=True "scope_name", flat=True
) )
) )
if len(self.scope) == 0:
self.scope = default_scope_names self.scope = default_scope_names
LOGGER.info( LOGGER.info(
"No scopes requested, defaulting to all configured scopes", scopes=self.scope "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 ( if SCOPE_OPENID not in self.scope and (
self.grant_type == GrantTypes.HYBRID self.grant_type == GrantTypes.HYBRID
or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN] or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN]
): ):
LOGGER.warning("Missing 'openid' scope.") LOGGER.warning("Missing 'openid' scope.")
raise AuthorizeError(self.redirect_uri, "invalid_scope", self.grant_type, self.state) 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): def check_nonce(self):
"""Nonce parameter validation.""" """Nonce parameter validation."""
@ -297,6 +327,9 @@ class AuthorizationFlowInitView(PolicyAccessView):
"""OAuth2 Flow initializer, checks access to application and starts flow""" """OAuth2 Flow initializer, checks access to application and starts flow"""
params: OAuthAuthorizationParams params: OAuthAuthorizationParams
# Enable GitHub compatibility (only allow for scopes which are handled
# differently for github compat)
github_compat = False
def pre_permission_check(self): def pre_permission_check(self):
"""Check prompt parameter before checking permission/authentication, """Check prompt parameter before checking permission/authentication,
@ -305,7 +338,9 @@ class AuthorizationFlowInitView(PolicyAccessView):
if len(self.request.GET) < 1: if len(self.request.GET) < 1:
raise Http404 raise Http404
try: try:
self.params = OAuthAuthorizationParams.from_request(self.request) self.params = OAuthAuthorizationParams.from_request(
self.request, github_compat=self.github_compat
)
except AuthorizeError as error: except AuthorizeError as error:
LOGGER.warning(error.description, redirect_uri=error.redirect_uri) LOGGER.warning(error.description, redirect_uri=error.redirect_uri)
raise RequestValidationError(error.get_response(self.request)) 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 # OpenID clients can specify a `prompt` parameter, and if its set to consent we
# need to inject a consent stage # need to inject a consent stage
if PROMPT_CONSENT in self.params.prompt: 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 # Plan does not have any consent stage, so we add an in-memory one
stage = ConsentStage( stage = ConsentStage(
name="OAuth2 Provider In-memory consent stage", name="OAuth2 Provider In-memory consent stage",

View File

@ -41,6 +41,7 @@ from authentik.providers.oauth2.constants import (
GRANT_TYPE_PASSWORD, GRANT_TYPE_PASSWORD,
GRANT_TYPE_REFRESH_TOKEN, GRANT_TYPE_REFRESH_TOKEN,
PKCE_METHOD_S256, PKCE_METHOD_S256,
SCOPE_OFFLINE_ACCESS,
TOKEN_TYPE, TOKEN_TYPE,
) )
from authentik.providers.oauth2.errors import DeviceCodeError, TokenError, UserAuthError from authentik.providers.oauth2.errors import DeviceCodeError, TokenError, UserAuthError
@ -459,7 +460,7 @@ class TokenView(View):
op="authentik.providers.oauth2.post.response", op="authentik.providers.oauth2.post.response",
): ):
if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE: 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()) return TokenResponse(self.create_code_response())
if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN: if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN:
LOGGER.debug("Refreshing refresh token") LOGGER.debug("Refreshing refresh token")
@ -489,13 +490,25 @@ class TokenView(View):
auth_time=self.params.authorization_code.auth_time, auth_time=self.params.authorization_code.auth_time,
session_id=self.params.authorization_code.session_id, session_id=self.params.authorization_code.session_id,
) )
access_token.id_token = IDToken.new( access_id_token = IDToken.new(
self.provider, self.provider,
access_token, access_token,
self.request, self.request,
) )
access_id_token.nonce = self.params.authorization_code.nonce
access_token.id_token = access_id_token
access_token.save() access_token.save()
response = {
"access_token": access_token.token,
"token_type": TOKEN_TYPE,
"expires_in": int(
timedelta_from_string(self.provider.access_token_validity).total_seconds()
),
"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_expiry = now + timedelta_from_string(self.provider.refresh_token_validity)
refresh_token = RefreshToken( refresh_token = RefreshToken(
user=self.params.authorization_code.user, user=self.params.authorization_code.user,
@ -514,24 +527,19 @@ class TokenView(View):
id_token.at_hash = access_token.at_hash id_token.at_hash = access_token.at_hash
refresh_token.id_token = id_token refresh_token.id_token = id_token
refresh_token.save() refresh_token.save()
response["refresh_token"] = refresh_token.token
# Delete old code # Delete old code
self.params.authorization_code.delete() self.params.authorization_code.delete()
return { 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),
}
def create_refresh_response(self) -> dict[str, Any]: def create_refresh_response(self) -> dict[str, Any]:
"""See https://datatracker.ietf.org/doc/html/rfc6749#section-6""" """See https://datatracker.ietf.org/doc/html/rfc6749#section-6"""
unauthorized_scopes = set(self.params.scope) - set(self.params.refresh_token.scope) unauthorized_scopes = set(self.params.scope) - set(self.params.refresh_token.scope)
if unauthorized_scopes: if unauthorized_scopes:
raise TokenError("invalid_scope") raise TokenError("invalid_scope")
if SCOPE_OFFLINE_ACCESS not in self.params.scope:
raise TokenError("invalid_scope")
now = timezone.now() now = timezone.now()
access_token_expiry = now + timedelta_from_string(self.provider.access_token_validity) access_token_expiry = now + timedelta_from_string(self.provider.access_token_validity)
access_token = AccessToken( access_token = AccessToken(
@ -630,6 +638,16 @@ class TokenView(View):
) )
access_token.save() access_token.save()
response = {
"access_token": access_token.token,
"token_type": TOKEN_TYPE,
"expires_in": int(
timedelta_from_string(self.provider.access_token_validity).total_seconds()
),
"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_expiry = now + timedelta_from_string(self.provider.refresh_token_validity)
refresh_token = RefreshToken( refresh_token = RefreshToken(
user=self.params.device_code.user, user=self.params.device_code.user,
@ -646,15 +664,8 @@ class TokenView(View):
id_token.at_hash = access_token.at_hash id_token.at_hash = access_token.at_hash
refresh_token.id_token = id_token refresh_token.id_token = id_token
refresh_token.save() refresh_token.save()
response["refresh_token"] = refresh_token.token
# Delete device code # Delete device code
self.params.device_code.delete() self.params.device_code.delete()
return { 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),
}

View File

@ -24,7 +24,10 @@ class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer):
def get_app_label_verbose(self, instance: GroupObjectPermission) -> str: def get_app_label_verbose(self, instance: GroupObjectPermission) -> str:
"""Get app label from permission's model""" """Get app label from permission's model"""
try:
return apps.get_app_config(instance.content_type.app_label).verbose_name 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: def get_model_verbose(self, instance: GroupObjectPermission) -> str:
"""Get model label from permission's model""" """Get model label from permission's model"""

View File

@ -24,7 +24,10 @@ class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer):
def get_app_label_verbose(self, instance: UserObjectPermission) -> str: def get_app_label_verbose(self, instance: UserObjectPermission) -> str:
"""Get app label from permission's model""" """Get app label from permission's model"""
try:
return apps.get_app_config(instance.content_type.app_label).verbose_name 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: def get_model_verbose(self, instance: UserObjectPermission) -> str:
"""Get model label from permission's model""" """Get model label from permission's model"""

View File

@ -109,6 +109,9 @@ class BoundSessionMiddleware(SessionMiddleware):
self.recheck_session_geo(configured_binding_geo, last_ip, new_ip) 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 # If we got to this point without any error being raised, we need to
# update the last saved IP to the current one # update the last saved IP to the current one
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 request.session[SESSION_KEY_LAST_IP] = new_ip
AuthenticatedSession.objects.filter(session_key=request.session.session_key).update( AuthenticatedSession.objects.filter(session_key=request.session.session_key).update(
last_ip=new_ip, last_user_agent=request.META.get("HTTP_USER_AGENT", "") last_ip=new_ip, last_user_agent=request.META.get("HTTP_USER_AGENT", "")

View File

@ -9011,6 +9011,12 @@
"prompt" "prompt"
], ],
"title": "Auth mode" "title": "Auth mode"
},
"maximum_connections": {
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"title": "Maximum connections"
} }
}, },
"required": [] "required": []

View File

@ -45,3 +45,14 @@ entries:
# groups is not part of the official userinfo schema, but is a quasi-standard # 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()], "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 {}

14
go.mod
View File

@ -23,15 +23,15 @@ require (
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.7.0 github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.18.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/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/wwt/guac v1.3.2 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/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.15.0 golang.org/x/oauth2 v0.16.0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.6.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab 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 v1.17.0 // indirect
go.opentelemetry.io/otel/metric v1.17.0 // indirect go.opentelemetry.io/otel/metric v1.17.0 // indirect
go.opentelemetry.io/otel/trace v1.17.0 // indirect go.opentelemetry.io/otel/trace v1.17.0 // indirect
golang.org/x/crypto v0.17.0 // indirect golang.org/x/crypto v0.18.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.31.0 // indirect

28
go.sum
View File

@ -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/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 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 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.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= 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.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 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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.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 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 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.5 h1:wIL3Q0jry1g4kRWpH/Dv1sQqhzuL4BLC+uP/Tar1P/g=
goauthentik.io/api/v3 v3.2023105.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= 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-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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/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-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.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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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.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.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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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-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-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-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-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-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.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -92,11 +92,11 @@ msgstr ""
msgid "Brands" msgid "Brands"
msgstr "" msgstr ""
#: authentik/core/api/providers.py:120 #: authentik/core/api/providers.py:122
msgid "SAML Provider from Metadata" msgid "SAML Provider from Metadata"
msgstr "" msgstr ""
#: authentik/core/api/providers.py:121 #: authentik/core/api/providers.py:123
msgid "Create a SAML Provider by importing its Metadata." msgid "Create a SAML Provider by importing its Metadata."
msgstr "" msgstr ""
@ -343,7 +343,7 @@ msgid "Powered by authentik"
msgstr "" msgstr ""
#: authentik/core/views/apps.py:53 #: 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/oauth2/views/device_init.py:70
#: authentik/providers/saml/views/sso.py:70 #: authentik/providers/saml/views/sso.py:70
#, python-format #, python-format
@ -372,6 +372,10 @@ msgstr ""
msgid "Certificate-Key Pairs" msgid "Certificate-Key Pairs"
msgstr "" msgstr ""
#: authentik/enterprise/api.py:33
msgid "Enterprise is required to create/update this object."
msgstr ""
#: authentik/enterprise/models.py:183 #: authentik/enterprise/models.py:183
msgid "License" msgid "License"
msgstr "" msgstr ""
@ -411,22 +415,30 @@ msgstr ""
msgid "RAC Providers" msgid "RAC Providers"
msgstr "" msgstr ""
#: authentik/enterprise/providers/rac/models.py:99 #: authentik/enterprise/providers/rac/models.py:100
msgid "RAC Endpoint" msgid "RAC Endpoint"
msgstr "" msgstr ""
#: authentik/enterprise/providers/rac/models.py:100 #: authentik/enterprise/providers/rac/models.py:101
msgid "RAC Endpoints" msgid "RAC Endpoints"
msgstr "" msgstr ""
#: authentik/enterprise/providers/rac/models.py:121 #: authentik/enterprise/providers/rac/models.py:122
msgid "RAC Property Mapping" msgid "RAC Property Mapping"
msgstr "" msgstr ""
#: authentik/enterprise/providers/rac/models.py:122 #: authentik/enterprise/providers/rac/models.py:123
msgid "RAC Property Mappings" msgid "RAC Property Mappings"
msgstr "" 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 #: authentik/events/models.py:289
msgid "Event" msgid "Event"
msgstr "" msgstr ""
@ -1282,7 +1294,7 @@ msgstr ""
msgid "Device Tokens" msgid "Device Tokens"
msgstr "" msgstr ""
#: authentik/providers/oauth2/views/authorize.py:450 #: authentik/providers/oauth2/views/authorize.py:485
#: authentik/providers/saml/views/flows.py:87 #: authentik/providers/saml/views/flows.py:87
#, python-format #, python-format
msgid "Redirecting to %(app)s..." msgid "Redirecting to %(app)s..."

View File

@ -19247,6 +19247,7 @@ paths:
- tr - tr
- tt - tt
- udm - udm
- ug
- uk - uk
- ur - ur
- uz - uz
@ -32108,6 +32109,10 @@ components:
Build actual launch URL (the provider itself does not have one, just Build actual launch URL (the provider itself does not have one, just
individual endpoints) individual endpoints)
readOnly: true readOnly: true
maximum_connections:
type: integer
maximum: 2147483647
minimum: -2147483648
required: required:
- auth_mode - auth_mode
- host - host
@ -32139,6 +32144,10 @@ components:
format: uuid format: uuid
auth_mode: auth_mode:
$ref: '#/components/schemas/AuthModeEnum' $ref: '#/components/schemas/AuthModeEnum'
maximum_connections:
type: integer
maximum: 2147483647
minimum: -2147483648
required: required:
- auth_mode - auth_mode
- host - host
@ -38118,6 +38127,10 @@ components:
format: uuid format: uuid
auth_mode: auth_mode:
$ref: '#/components/schemas/AuthModeEnum' $ref: '#/components/schemas/AuthModeEnum'
maximum_connections:
type: integer
maximum: 2147483647
minimum: -2147483648
PatchedEventMatcherPolicyRequest: PatchedEventMatcherPolicyRequest:
type: object type: object
description: Event Matcher Policy Serializer description: Event Matcher Policy Serializer
@ -43786,6 +43799,9 @@ components:
type: string type: string
model_name: model_name:
type: string type: string
requires_enterprise:
type: boolean
default: false
required: required:
- component - component
- description - description

View File

@ -74,7 +74,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent" slug="default-provider-authorization-implicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
@ -82,8 +82,8 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
Application.objects.create( Application.objects.create(
name="Grafana", name=generate_id(),
slug="grafana", slug=generate_id(),
provider=provider, provider=provider,
) )
@ -129,7 +129,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent" slug="default-provider-authorization-explicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
@ -137,8 +137,8 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
app = Application.objects.create( app = Application.objects.create(
name="Grafana", name=generate_id(),
slug="grafana", slug=generate_id(),
provider=provider, provider=provider,
) )
@ -200,7 +200,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent" slug="default-provider-authorization-explicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
@ -208,8 +208,8 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
app = Application.objects.create( app = Application.objects.create(
name="Grafana", name=generate_id(),
slug="grafana", slug=generate_id(),
provider=provider, provider=provider,
) )

View File

@ -14,6 +14,7 @@ from authentik.lib.generators import generate_id, generate_key
from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import ( from authentik.providers.oauth2.constants import (
SCOPE_OFFLINE_ACCESS,
SCOPE_OPENID, SCOPE_OPENID,
SCOPE_OPENID_EMAIL, SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE, SCOPE_OPENID_PROFILE,
@ -80,7 +81,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent" slug="default-provider-authorization-implicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
@ -90,12 +91,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()
Application.objects.create( Application.objects.create(
name="Grafana", name=generate_id(),
slug=self.app_slug, slug=self.app_slug,
provider=provider, provider=provider,
) )
@ -113,12 +119,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
"default/flow-default-provider-authorization-implicit-consent.yaml", @apply_blueprint("system/providers-oauth2.yaml")
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_consent_implied(self): 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)"""
@ -128,7 +130,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent" slug="default-provider-authorization-implicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
@ -138,11 +140,16 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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( Application.objects.create(
name="Grafana", name=generate_id(),
slug=self.app_slug, slug=self.app_slug,
provider=provider, provider=provider,
) )
@ -174,12 +181,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
"default/flow-default-provider-authorization-implicit-consent.yaml", @apply_blueprint("system/providers-oauth2.yaml")
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_logout(self): def test_authorization_logout(self):
"""test OpenID Provider flow with logout""" """test OpenID Provider flow with logout"""
@ -189,7 +192,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent" slug="default-provider-authorization-implicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
@ -199,12 +202,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()
Application.objects.create( Application.objects.create(
name="Grafana", name=generate_id(),
slug=self.app_slug, slug=self.app_slug,
provider=provider, provider=provider,
) )
@ -244,12 +252,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
"default/flow-default-provider-authorization-explicit-consent.yaml", @apply_blueprint("system/providers-oauth2.yaml")
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_consent_explicit(self): def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)""" """test OpenID Provider flow (default authorization flow with explicit consent)"""
@ -259,7 +263,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent" slug="default-provider-authorization-explicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
@ -269,12 +273,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()
app = Application.objects.create( app = Application.objects.create(
name="Grafana", name=generate_id(),
slug=self.app_slug, slug=self.app_slug,
provider=provider, provider=provider,
) )
@ -323,12 +332,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
"default/flow-default-provider-authorization-explicit-consent.yaml", @apply_blueprint("system/providers-oauth2.yaml")
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_denied(self): def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)""" """test OpenID Provider flow (default authorization with access deny)"""
@ -338,7 +343,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent" slug="default-provider-authorization-explicit-consent"
) )
provider = OAuth2Provider.objects.create( provider = OAuth2Provider.objects.create(
name="grafana", name=generate_id(),
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
@ -348,12 +353,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()
app = Application.objects.create( app = Application.objects.create(
name="Grafana", name=generate_id(),
slug=self.app_slug, slug=self.app_slug,
provider=provider, provider=provider,
) )

View File

@ -15,6 +15,7 @@ from authentik.lib.generators import generate_id, generate_key
from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import ( from authentik.providers.oauth2.constants import (
SCOPE_OFFLINE_ACCESS,
SCOPE_OPENID, SCOPE_OPENID,
SCOPE_OPENID_EMAIL, SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE, SCOPE_OPENID_PROFILE,
@ -29,7 +30,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
def setUp(self): def setUp(self):
self.client_id = generate_id() self.client_id = generate_id()
self.client_secret = generate_key() self.client_secret = generate_key()
self.application_slug = "test" self.application_slug = generate_id()
super().setUp() super().setUp()
def setup_client(self) -> Container: def setup_client(self) -> Container:
@ -37,7 +38,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
sleep(1) sleep(1)
client: DockerClient = from_env() client: DockerClient = from_env()
container = client.containers.run( container = client.containers.run(
image="ghcr.io/beryju/oidc-test-client:1.3", image="ghcr.io/beryju/oidc-test-client:2.1",
detach=True, detach=True,
ports={ ports={
"9009": "9009", "9009": "9009",
@ -56,9 +57,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_redirect_uri_error(self): def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)""" """test OpenID Provider flow (invalid redirect URI, check error message)"""
@ -78,10 +77,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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( Application.objects.create(
name=self.application_slug, name=self.application_slug,
slug=self.application_slug, slug=self.application_slug,
@ -101,13 +104,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_implied(self): 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) sleep(1)
# Bootstrap all needed objects # Bootstrap all needed objects
authorization_flow = Flow.objects.get( authorization_flow = Flow.objects.get(
@ -124,11 +126,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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(
Application.objects.create(
name=self.application_slug, name=self.application_slug,
slug=self.application_slug, slug=self.application_slug,
provider=provider, provider=provider,
@ -137,6 +143,20 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
self.driver.get("http://localhost:9009") self.driver.get("http://localhost:9009")
self.login() 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.presence_of_element_located((By.CSS_SELECTOR, "pre")))
self.wait.until(ec.text_to_be_present_in_element((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) 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-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_explicit(self): def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)""" """test OpenID Provider flow (default authorization flow with explicit consent)"""
sleep(1) sleep(1)
@ -178,10 +196,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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( app = Application.objects.create(
name=self.application_slug, name=self.application_slug,
slug=self.application_slug, slug=self.application_slug,
@ -224,9 +246,8 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
"default/flow-default-provider-authorization-explicit-consent.yaml", @apply_blueprint("system/providers-oauth2.yaml")
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_denied(self): def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)""" """test OpenID Provider flow (default authorization with access deny)"""
@ -246,10 +267,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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( app = Application.objects.create(
name=self.application_slug, name=self.application_slug,
slug=self.application_slug, slug=self.application_slug,

View File

@ -15,6 +15,7 @@ from authentik.lib.generators import generate_id, generate_key
from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import ( from authentik.providers.oauth2.constants import (
SCOPE_OFFLINE_ACCESS,
SCOPE_OPENID, SCOPE_OPENID,
SCOPE_OPENID_EMAIL, SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE, SCOPE_OPENID_PROFILE,
@ -37,7 +38,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
sleep(1) sleep(1)
client: DockerClient = from_env() client: DockerClient = from_env()
container = client.containers.run( container = client.containers.run(
image="ghcr.io/beryju/oidc-test-client:1.3", image="ghcr.io/beryju/oidc-test-client:2.1",
detach=True, detach=True,
ports={ ports={
"9009": "9009", "9009": "9009",
@ -56,9 +57,8 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
"default/flow-default-provider-authorization-implicit-consent.yaml", @apply_blueprint("system/providers-oauth2.yaml")
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_redirect_uri_error(self): def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)""" """test OpenID Provider flow (invalid redirect URI, check error message)"""
@ -78,7 +78,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()
@ -101,11 +106,9 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_implied(self): 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)"""
sleep(1) sleep(1)
@ -124,7 +127,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()
@ -150,11 +158,9 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_explicit(self): def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)""" """test OpenID Provider flow (default authorization flow with explicit consent)"""
sleep(1) sleep(1)
@ -173,7 +179,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()
@ -215,9 +226,8 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
"default/flow-default-provider-authorization-explicit-consent.yaml", @apply_blueprint("system/providers-oauth2.yaml")
)
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_denied(self): def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)""" """test OpenID Provider flow (default authorization with access deny)"""
@ -237,7 +247,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
) )
provider.property_mappings.set( provider.property_mappings.set(
ScopeMapping.objects.filter( 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() provider.save()

View File

@ -7,8 +7,8 @@
"name": "@goauthentik/web-tests", "name": "@goauthentik/web-tests",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.17.0", "@typescript-eslint/parser": "^6.18.1",
"@wdio/cli": "^8.27.1", "@wdio/cli": "^8.27.1",
"@wdio/local-runner": "^8.27.0", "@wdio/local-runner": "^8.27.0",
"@wdio/mocha-framework": "^8.27.0", "@wdio/mocha-framework": "^8.27.0",
@ -946,16 +946,16 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz",
"integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.5.1", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.17.0", "@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/type-utils": "6.17.0", "@typescript-eslint/type-utils": "6.18.1",
"@typescript-eslint/utils": "6.17.0", "@typescript-eslint/utils": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0", "@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.4", "ignore": "^5.2.4",
@ -981,15 +981,15 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz",
"integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "6.17.0", "@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.17.0", "@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0", "@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -1009,13 +1009,13 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0" "@typescript-eslint/visitor-keys": "6.18.1"
}, },
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -1026,13 +1026,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz",
"integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "6.17.0", "@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/utils": "6.17.0", "@typescript-eslint/utils": "6.18.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
@ -1053,9 +1053,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -1066,13 +1066,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0", "@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -1118,17 +1118,17 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz",
"integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.17.0", "@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.17.0", "@typescript-eslint/typescript-estree": "6.18.1",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
@ -1143,12 +1143,12 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {

View File

@ -4,8 +4,8 @@
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.17.0", "@typescript-eslint/parser": "^6.18.1",
"@wdio/cli": "^8.27.1", "@wdio/cli": "^8.27.1",
"@wdio/local-runner": "^8.27.0", "@wdio/local-runner": "^8.27.0",
"@wdio/mocha-framework": "^8.27.0", "@wdio/mocha-framework": "^8.27.0",

View File

@ -3,6 +3,92 @@
This is the default UI for the authentik server. The documentation is going to be a little sparse 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. 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 # Comments
**NOTE:** The comments in this section are for specific changes to this repository that cannot be **NOTE:** The comments in this section are for specific changes to this repository that cannot be

View File

@ -3,15 +3,18 @@
"sourceLocale": "en", "sourceLocale": "en",
"targetLocales": [ "targetLocales": [
"en", "en",
"pseudo-LOCALE", "de",
"fr",
"tr",
"es", "es",
"fr",
"ko",
"nl",
"pl", "pl",
"zh_TW", "tr",
"zh-Hans", "zh-Hans",
"zh-Hant", "zh-Hant",
"de" "zh-CN",
"zh_TW",
"pseudo-LOCALE"
], ],
"tsConfig": "./tsconfig.json", "tsConfig": "./tsconfig.json",
"output": { "output": {

350
web/package-lock.json generated
View File

@ -17,15 +17,15 @@
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.3", "@formatjs/intl-listformat": "^7.5.3",
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2023.10.5-1703968412", "@goauthentik/api": "^2023.10.5-1704382057",
"@lit-labs/context": "^0.4.0", "@lit-labs/context": "^0.4.0",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4", "@lit/localize": "^0.11.4",
"@open-wc/lit-helpers": "^0.6.0", "@open-wc/lit-helpers": "^0.6.0",
"@patternfly/elements": "^2.4.0", "@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.91.0", "@sentry/browser": "^7.92.0",
"@sentry/tracing": "^7.91.0", "@sentry/tracing": "^7.92.0",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
@ -50,7 +50,7 @@
"@babel/plugin-transform-private-methods": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3",
"@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-private-property-in-object": "^7.23.4",
"@babel/plugin-transform-runtime": "^7.23.7", "@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", "@babel/preset-typescript": "^7.23.3",
"@hcaptcha/types": "^1.0.3", "@hcaptcha/types": "^1.0.3",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jackfranklin/rollup-plugin-markdown": "^0.4.0",
@ -74,8 +74,8 @@
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.7", "@types/grecaptcha": "^3.0.7",
"@types/guacamole-common-js": "1.5.2", "@types/guacamole-common-js": "1.5.2",
"@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.17.0", "@typescript-eslint/parser": "^6.18.1",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -85,21 +85,21 @@
"eslint-plugin-lit": "^1.11.0", "eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
"eslint-plugin-storybook": "^0.6.15", "eslint-plugin-storybook": "^0.6.15",
"lit-analyzer": "^2.0.2", "lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"pseudolocale": "^2.0.0", "pseudolocale": "^2.0.0",
"pyright": "=1.1.338", "pyright": "=1.1.338",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rollup": "^4.9.2", "rollup": "^4.9.4",
"rollup-plugin-copy": "^3.5.0", "rollup-plugin-copy": "^3.5.0",
"rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-cssimport": "^1.0.3",
"rollup-plugin-modify": "^3.0.0", "rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0", "rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.7", "storybook": "^7.6.7",
"storybook-addon-mock": "^4.3.0", "storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.1", "ts-lit-plugin": "^2.0.2",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"turnstile-types": "^1.2.0", "turnstile-types": "^1.2.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
@ -1119,16 +1119,15 @@
} }
}, },
"node_modules/@babel/plugin-transform-classes": { "node_modules/@babel/plugin-transform-classes": {
"version": "7.23.5", "version": "7.23.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz",
"integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5", "@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-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0", "@babel/helper-function-name": "^7.23.0",
"@babel/helper-optimise-call-expression": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
"@babel/helper-replace-supers": "^7.22.20", "@babel/helper-replace-supers": "^7.22.20",
"@babel/helper-split-export-declaration": "^7.22.6", "@babel/helper-split-export-declaration": "^7.22.6",
@ -1833,9 +1832,9 @@
} }
}, },
"node_modules/@babel/preset-env": { "node_modules/@babel/preset-env": {
"version": "7.23.7", "version": "7.23.8",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz",
"integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.23.5", "@babel/compat-data": "^7.23.5",
@ -1871,7 +1870,7 @@
"@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-block-scoping": "^7.23.4",
"@babel/plugin-transform-class-properties": "^7.23.3", "@babel/plugin-transform-class-properties": "^7.23.3",
"@babel/plugin-transform-class-static-block": "^7.23.4", "@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-computed-properties": "^7.23.3",
"@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3",
"@babel/plugin-transform-dotall-regex": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3",
@ -2913,9 +2912,9 @@
} }
}, },
"node_modules/@goauthentik/api": { "node_modules/@goauthentik/api": {
"version": "2023.10.5-1703968412", "version": "2023.10.5-1704382057",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.5-1703968412.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.5-1704382057.tgz",
"integrity": "sha512-/2QDgGkWGXOYDqH49/2hNs+U8TqdE94hkMrJc8A6L+NAy8x/zKAY39eUHs85jmwt013N5duD/jKiJsRftHsDig==" "integrity": "sha512-nzmAQgTrFXiOwKDeHhq1gAfIMRilAcDPmNvjhqoQc3GQfWs5GG2lAGzIewWyxsfxNABsg+I0BYTPxN7ffD6tXw=="
}, },
"node_modules/@hcaptcha/types": { "node_modules/@hcaptcha/types": {
"version": "1.0.3", "version": "1.0.3",
@ -4582,9 +4581,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz",
"integrity": "sha512-RKzxFxBHq9ysZ83fn8Iduv3A283K7zPPYuhL/z9CQuyFrjwpErJx0h4aeb/bnJ+q29GRLgJpY66ceQ/Wcsn3wA==", "integrity": "sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -4595,9 +4594,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz",
"integrity": "sha512-yZ+MUbnwf3SHNWQKJyWh88ii2HbuHCFQnAYTeeO1Nb8SyEiWASEi5dQUygt3ClHWtA9My9RQAYkjvrsZ0WK8Xg==", "integrity": "sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4608,9 +4607,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz",
"integrity": "sha512-vqJ/pAUh95FLc/G/3+xPqlSBgilPnauVf2EXOQCZzhZJCXDXt/5A8mH/OzU6iWhb3CNk5hPJrh8pqJUPldN5zw==", "integrity": "sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4621,9 +4620,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz",
"integrity": "sha512-otPHsN5LlvedOprd3SdfrRNhOahhVBwJpepVKUN58L0RnC29vOAej1vMEaVU6DadnpjivVsNTM5eNt0CcwTahw==", "integrity": "sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4634,9 +4633,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz",
"integrity": "sha512-ewG5yJSp+zYKBYQLbd1CUA7b1lSfIdo9zJShNTyc2ZP1rcPrqyZcNlsHgs7v1zhgfdS+kW0p5frc0aVqhZCiYQ==", "integrity": "sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -4647,9 +4646,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz",
"integrity": "sha512-pL6QtV26W52aCWTG1IuFV3FMPL1m4wbsRG+qijIvgFO/VBsiXJjDPE/uiMdHBAO6YcpV4KvpKtd0v3WFbaxBtg==", "integrity": "sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4660,9 +4659,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz",
"integrity": "sha512-On+cc5EpOaTwPSNetHXBuqylDW+765G/oqB9xGmWU3npEhCh8xu0xqHGUA+4xwZLqBbIZNcBlKSIYfkBm6ko7g==", "integrity": "sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4673,9 +4672,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz",
"integrity": "sha512-Wnx/IVMSZ31D/cO9HSsU46FjrPWHqtdF8+0eyZ1zIB5a6hXaZXghUKpRrC4D5DcRTZOjml2oBhXoqfGYyXKipw==", "integrity": "sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -4686,9 +4685,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz",
"integrity": "sha512-ym5x1cj4mUAMBummxxRkI4pG5Vht1QMsJexwGP8547TZ0sox9fCLDHw9KCH9c1FO5d9GopvkaJsBIOkTKxksdw==", "integrity": "sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4699,9 +4698,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz",
"integrity": "sha512-m0hYELHGXdYx64D6IDDg/1vOJEaiV8f1G/iO+tejvRCJNSwK4jJ15e38JQy5Q6dGkn1M/9KcyEOwqmlZ2kqaZg==", "integrity": "sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4712,9 +4711,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz",
"integrity": "sha512-x1CWburlbN5JjG+juenuNa4KdedBdXLjZMp56nHFSHTOsb/MI2DYiGzLtRGHNMyydPGffGId+VgjOMrcltOksA==", "integrity": "sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4725,9 +4724,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz",
"integrity": "sha512-VVzCB5yXR1QlfsH1Xw1zdzQ4Pxuzv+CPr5qpElpKhVxlxD3CRdfubAG9mJROl6/dmj5gVYDDWk8sC+j9BI9/kQ==", "integrity": "sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -4738,9 +4737,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz",
"integrity": "sha512-SYRedJi+mweatroB+6TTnJYLts0L0bosg531xnQWtklOI6dezEagx4Q0qDyvRdK+qgdA3YZpjjGuPFtxBmddBA==", "integrity": "sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4751,98 +4750,98 @@
] ]
}, },
"node_modules/@sentry-internal/feedback": { "node_modules/@sentry-internal/feedback": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.92.0.tgz",
"integrity": "sha512-SJKTSaz68F5YIwF79EttBm915M2LnacgZMYRnRumyTmMKnebGhYQLwWbZdpaDvOa1U18dgRajDX8Qed/8A3tXw==", "integrity": "sha512-/jEALRtVqboxB9kcK2tag8QCO6XANTlGBb9RV3oeGXJe0DDNJXRq6wVZbfgztXJRrfgx4XVDcNt1pRVoGGG++g==",
"dependencies": { "dependencies": {
"@sentry/core": "7.91.0", "@sentry/core": "7.92.0",
"@sentry/types": "7.91.0", "@sentry/types": "7.92.0",
"@sentry/utils": "7.91.0" "@sentry/utils": "7.92.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@sentry-internal/tracing": { "node_modules/@sentry-internal/tracing": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.92.0.tgz",
"integrity": "sha512-JH5y6gs6BS0its7WF2DhySu7nkhPDfZcdpAXldxzIlJpqFkuwQKLU5nkYJpiIyZz1NHYYtW5aum2bV2oCOdDRA==", "integrity": "sha512-ur55vPcUUUWFUX4eVLNP71ohswK7ZZpleNZw9Y1GfLqyI+0ILQUwjtzqItJrdClvVsdRZJMRmDV40Hp9Lbb9mA==",
"dependencies": { "dependencies": {
"@sentry/core": "7.91.0", "@sentry/core": "7.92.0",
"@sentry/types": "7.91.0", "@sentry/types": "7.92.0",
"@sentry/utils": "7.91.0" "@sentry/utils": "7.92.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.92.0.tgz",
"integrity": "sha512-lJv3x/xekzC/biiyAsVCioq2XnKNOZhI6jY3ZzLJZClYV8eKRi7D3KCsHRvMiCdGak1d/6sVp8F4NYY+YiWy1Q==", "integrity": "sha512-loMr02/zQ38u8aQhYLtIBg0i5n3ps2e3GUXrt3CdsJQdkRYfa62gcrE7SzvoEpMVHTk7VOI4fWGht8cWw/1k3A==",
"dependencies": { "dependencies": {
"@sentry-internal/feedback": "7.91.0", "@sentry-internal/feedback": "7.92.0",
"@sentry-internal/tracing": "7.91.0", "@sentry-internal/tracing": "7.92.0",
"@sentry/core": "7.91.0", "@sentry/core": "7.92.0",
"@sentry/replay": "7.91.0", "@sentry/replay": "7.92.0",
"@sentry/types": "7.91.0", "@sentry/types": "7.92.0",
"@sentry/utils": "7.91.0" "@sentry/utils": "7.92.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.92.0.tgz",
"integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", "integrity": "sha512-1Tly7YB2I1byI5xb0Cwrxs56Rhww+6mQ7m9P7rTmdC3/ijOzbEoohtYIUPwcooCEarpbEJe/tAayRx6BrH2UbQ==",
"dependencies": { "dependencies": {
"@sentry/types": "7.91.0", "@sentry/types": "7.92.0",
"@sentry/utils": "7.91.0" "@sentry/utils": "7.92.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/replay": { "node_modules/@sentry/replay": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.92.0.tgz",
"integrity": "sha512-XwbesnLLNtaVXKtDoyBB96GxJuhGi9zy3a662Ba/McmumCnkXrMQYpQPh08U7MgkTyDRgjDwm7PXDhiKpcb03g==", "integrity": "sha512-G1t9Uvc9cR8VpNkElwvHIMGzykjIKikb10n0tfVd3e+rBPMCCjCPWOduwG6jZYxcvCjTpqmJh6NSLXxL/Mt4JA==",
"dependencies": { "dependencies": {
"@sentry-internal/tracing": "7.91.0", "@sentry-internal/tracing": "7.92.0",
"@sentry/core": "7.91.0", "@sentry/core": "7.92.0",
"@sentry/types": "7.91.0", "@sentry/types": "7.92.0",
"@sentry/utils": "7.91.0" "@sentry/utils": "7.92.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@sentry/tracing": { "node_modules/@sentry/tracing": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.92.0.tgz",
"integrity": "sha512-IlSAMvqfCL/2TwwN4Tmk6bGMgilGruv5oIJ1GMenVZk53bHwjpjzMbd0ms8+S5zJwAgTQXoCbRhaFFrNmptteQ==", "integrity": "sha512-1+TFFPVEdax4dNi68gin6MENiyGe9mOuNXfjulrP5eCzUEByus5HAxeDI/LLQ1hArfn048AzwSwKUsS2fO5sbg==",
"dependencies": { "dependencies": {
"@sentry-internal/tracing": "7.91.0" "@sentry-internal/tracing": "7.92.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/types": { "node_modules/@sentry/types": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.92.0.tgz",
"integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", "integrity": "sha512-APmSOuZuoRGpbPpPeYIbMSplPjiWNLZRQa73QiXuTflW4Tu/ItDlU8hOa2+A6JKVkJCuD2EN6yUrxDGSMyNXeg==",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/utils": { "node_modules/@sentry/utils": {
"version": "7.91.0", "version": "7.92.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.92.0.tgz",
"integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", "integrity": "sha512-3nEfrQ1z28b/2zgFGANPh5yMVtgwXmrasZxTvKbrAj+KWJpjrJHrIR84r9W277J44NMeZ5RhRW2uoDmuBslPnA==",
"dependencies": { "dependencies": {
"@sentry/types": "7.91.0" "@sentry/types": "7.92.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -7265,9 +7264,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.1", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true "dev": true
}, },
"node_modules/@types/express": { "node_modules/@types/express": {
@ -7576,16 +7575,16 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz",
"integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.5.1", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.17.0", "@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/type-utils": "6.17.0", "@typescript-eslint/type-utils": "6.18.1",
"@typescript-eslint/utils": "6.17.0", "@typescript-eslint/utils": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0", "@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.4", "ignore": "^5.2.4",
@ -7644,15 +7643,15 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz",
"integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "6.17.0", "@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.17.0", "@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0", "@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -7672,13 +7671,13 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0" "@typescript-eslint/visitor-keys": "6.18.1"
}, },
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -7689,13 +7688,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz",
"integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "6.17.0", "@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/utils": "6.17.0", "@typescript-eslint/utils": "6.18.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
@ -7716,9 +7715,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -7729,13 +7728,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.17.0", "@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -7814,17 +7813,17 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz",
"integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.17.0", "@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.17.0", "@typescript-eslint/typescript-estree": "6.18.1",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
@ -7872,12 +7871,12 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.17.0", "@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
@ -11456,9 +11455,9 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.2", "version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -13327,9 +13326,9 @@
} }
}, },
"node_modules/lit-analyzer": { "node_modules/lit-analyzer": {
"version": "2.0.2", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/lit-analyzer/-/lit-analyzer-2.0.2.tgz", "resolved": "https://registry.npmjs.org/lit-analyzer/-/lit-analyzer-2.0.3.tgz",
"integrity": "sha512-Is3cx8ypCVq5uNl8EKkPdlLuV3HDVntDVUeLNQlzTM2Je3uG5wHcn+06NB+yhCoa4rhwwXCjprU/7g21CSFqOA==", "integrity": "sha512-XiAjnwVipNrKav7r3CSEZpWt+mwYxrhPRVC7h8knDmn/HWTzzWJvPe+mwBcL2brn4xhItAMzZhFC8tzzqHKmiQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vscode/web-custom-data": "^0.4.2", "@vscode/web-custom-data": "^0.4.2",
@ -16419,10 +16418,13 @@
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.9.2", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.4.tgz",
"integrity": "sha512-66RB8OtFKUTozmVEh3qyNfH+b+z2RXBVloqO2KCC/pjFaGaHtxP9fVfOQKPSGXg2mElmjmxjW/fZ7iKrEpMH5Q==", "integrity": "sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g==",
"dev": true, "dev": true,
"dependencies": {
"@types/estree": "1.0.5"
},
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },
@ -16431,19 +16433,19 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.9.2", "@rollup/rollup-android-arm-eabi": "4.9.4",
"@rollup/rollup-android-arm64": "4.9.2", "@rollup/rollup-android-arm64": "4.9.4",
"@rollup/rollup-darwin-arm64": "4.9.2", "@rollup/rollup-darwin-arm64": "4.9.4",
"@rollup/rollup-darwin-x64": "4.9.2", "@rollup/rollup-darwin-x64": "4.9.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.9.2", "@rollup/rollup-linux-arm-gnueabihf": "4.9.4",
"@rollup/rollup-linux-arm64-gnu": "4.9.2", "@rollup/rollup-linux-arm64-gnu": "4.9.4",
"@rollup/rollup-linux-arm64-musl": "4.9.2", "@rollup/rollup-linux-arm64-musl": "4.9.4",
"@rollup/rollup-linux-riscv64-gnu": "4.9.2", "@rollup/rollup-linux-riscv64-gnu": "4.9.4",
"@rollup/rollup-linux-x64-gnu": "4.9.2", "@rollup/rollup-linux-x64-gnu": "4.9.4",
"@rollup/rollup-linux-x64-musl": "4.9.2", "@rollup/rollup-linux-x64-musl": "4.9.4",
"@rollup/rollup-win32-arm64-msvc": "4.9.2", "@rollup/rollup-win32-arm64-msvc": "4.9.4",
"@rollup/rollup-win32-ia32-msvc": "4.9.2", "@rollup/rollup-win32-ia32-msvc": "4.9.4",
"@rollup/rollup-win32-x64-msvc": "4.9.2", "@rollup/rollup-win32-x64-msvc": "4.9.4",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -17835,9 +17837,9 @@
} }
}, },
"node_modules/ts-lit-plugin": { "node_modules/ts-lit-plugin": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/ts-lit-plugin/-/ts-lit-plugin-2.0.1.tgz", "resolved": "https://registry.npmjs.org/ts-lit-plugin/-/ts-lit-plugin-2.0.2.tgz",
"integrity": "sha512-Y5G03aDiMYHMLzoZ50kdeVkzgVig2mBw6PVY2oI9PcWl3ONTcDyYq6rJ0QzhlACYWP8sT0dmaPMsHMObgNNvvg==", "integrity": "sha512-DPXlVxhjWHxg8AyBLcfSYt2JXgpANV1ssxxwjY98o26gD8MzeiM68HFW9c2VeDd1CjoR3w7B/6/uKxwBQe+ioA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"lit-analyzer": "^2.0.1", "lit-analyzer": "^2.0.1",

View File

@ -42,15 +42,15 @@
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.3", "@formatjs/intl-listformat": "^7.5.3",
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2023.10.5-1703968412", "@goauthentik/api": "^2023.10.5-1704382057",
"@lit-labs/context": "^0.4.0", "@lit-labs/context": "^0.4.0",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4", "@lit/localize": "^0.11.4",
"@open-wc/lit-helpers": "^0.6.0", "@open-wc/lit-helpers": "^0.6.0",
"@patternfly/elements": "^2.4.0", "@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.91.0", "@sentry/browser": "^7.92.0",
"@sentry/tracing": "^7.91.0", "@sentry/tracing": "^7.92.0",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
@ -75,7 +75,7 @@
"@babel/plugin-transform-private-methods": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3",
"@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-private-property-in-object": "^7.23.4",
"@babel/plugin-transform-runtime": "^7.23.7", "@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", "@babel/preset-typescript": "^7.23.3",
"@hcaptcha/types": "^1.0.3", "@hcaptcha/types": "^1.0.3",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jackfranklin/rollup-plugin-markdown": "^0.4.0",
@ -99,8 +99,8 @@
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.7", "@types/grecaptcha": "^3.0.7",
"@types/guacamole-common-js": "1.5.2", "@types/guacamole-common-js": "1.5.2",
"@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.17.0", "@typescript-eslint/parser": "^6.18.1",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -110,21 +110,21 @@
"eslint-plugin-lit": "^1.11.0", "eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
"eslint-plugin-storybook": "^0.6.15", "eslint-plugin-storybook": "^0.6.15",
"lit-analyzer": "^2.0.2", "lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"pseudolocale": "^2.0.0", "pseudolocale": "^2.0.0",
"pyright": "=1.1.338", "pyright": "=1.1.338",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rollup": "^4.9.2", "rollup": "^4.9.4",
"rollup-plugin-copy": "^3.5.0", "rollup-plugin-copy": "^3.5.0",
"rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-cssimport": "^1.0.3",
"rollup-plugin-modify": "^3.0.0", "rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0", "rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.7", "storybook": "^7.6.7",
"storybook-addon-mock": "^4.3.0", "storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.1", "ts-lit-plugin": "^2.0.2",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"turnstile-types": "^1.2.0", "turnstile-types": "^1.2.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",

View File

@ -7,7 +7,7 @@ import {
import { configureSentry } from "@goauthentik/common/sentry"; import { configureSentry } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { WebsocketClient } from "@goauthentik/common/ws"; 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/ak-locale-context";
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
import "@goauthentik/elements/messages/MessageContainer"; import "@goauthentik/elements/messages/MessageContainer";

View File

@ -1,23 +1,25 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants"; import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
import { AKElement } from "@goauthentik/elements/Base"; 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 { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle"; import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
import { spread } from "@open-wc/lit-helpers"; import { spread } from "@open-wc/lit-helpers";
import { consume } from "@lit-labs/context";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
import { TemplateResult, html, nothing } from "lit"; import { TemplateResult, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { map } from "lit/directives/map.js"; import { map } from "lit/directives/map.js";
import { AdminApi, CapabilitiesEnum, CoreApi, UiThemeEnum, Version } from "@goauthentik/api"; import { AdminApi, CoreApi, UiThemeEnum, Version } from "@goauthentik/api";
import type { Config, SessionUser, UserSelf } from "@goauthentik/api"; import type { SessionUser, UserSelf } from "@goauthentik/api";
@customElement("ak-admin-sidebar") @customElement("ak-admin-sidebar")
export class AkAdminSidebar extends AKElement { export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
@property({ type: Boolean, reflect: true }) @property({ type: Boolean, reflect: true })
open = true; open = true;
@ -27,9 +29,6 @@ export class AkAdminSidebar extends AKElement {
@state() @state()
impersonation: UserSelf["username"] | null = null; impersonation: UserSelf["username"] | null = null;
@consume({ context: authentikConfigContext })
public config!: Config;
constructor() { constructor() {
super(); super();
new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => { new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => {
@ -201,7 +200,7 @@ export class AkAdminSidebar extends AKElement {
} }
renderEnterpriseMessage() { renderEnterpriseMessage() {
return this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise) return this.can(CapabilitiesEnum.IsEnterprise)
? html` ? html`
<ak-sidebar-item> <ak-sidebar-item>
<span slot="label">${msg("Enterprise")}</span> <span slot="label">${msg("Enterprise")}</span>

View File

@ -74,10 +74,7 @@ export class AdminOverviewPage extends AKElement {
} }
render(): TemplateResult { render(): TemplateResult {
let name = this.user?.user.username; const name = this.user?.user.name ?? this.user?.user.username;
if (this.user?.user.name) {
name = this.user.user.name;
}
return html`<ak-page-header icon="" header="" description=${msg("General system status")}> return html`<ak-page-header icon="" header="" description=${msg("General system status")}>
<span slot="header"> ${msg(str`Welcome, ${name}.`)} </span> <span slot="header"> ${msg(str`Welcome, ${name}.`)} </span>
</ak-page-header> </ak-page-header>

View File

@ -1,13 +1,16 @@
import "@goauthentik/admin/applications/ProviderSelectModal"; import "@goauthentik/admin/applications/ProviderSelectModal";
import { iconHelperText } from "@goauthentik/admin/helperText"; 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 { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-file-input"; import "@goauthentik/components/ak-file-input";
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/ModalForm"; import "@goauthentik/elements/forms/ModalForm";
@ -22,13 +25,7 @@ import { TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { import { Application, CoreApi, PolicyEngineMode, Provider } from "@goauthentik/api";
Application,
CapabilitiesEnum,
CoreApi,
PolicyEngineMode,
Provider,
} from "@goauthentik/api";
import "./components/ak-backchannel-input"; import "./components/ak-backchannel-input";
import "./components/ak-provider-search-input"; import "./components/ak-provider-search-input";
@ -48,7 +45,7 @@ export const policyOptions = [
]; ];
@customElement("ak-application-form") @customElement("ak-application-form")
export class ApplicationForm extends ModelForm<Application, string> { export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Application, string>) {
constructor() { constructor() {
super(); super();
this.handleConfirmBackchannelProviders = this.handleConfirmBackchannelProviders.bind(this); this.handleConfirmBackchannelProviders = this.handleConfirmBackchannelProviders.bind(this);
@ -93,8 +90,7 @@ export class ApplicationForm extends ModelForm<Application, string> {
applicationRequest: data, applicationRequest: data,
}); });
} }
const c = await config(); if (this.can(CapabilitiesEnum.CanSaveMedia)) {
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
const icon = this.getFormFiles()["metaIcon"]; const icon = this.getFormFiles()["metaIcon"];
if (icon || this.clearIcon) { if (icon || this.clearIcon) {
await new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({ await new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({
@ -140,21 +136,21 @@ export class ApplicationForm extends ModelForm<Application, string> {
return html`<form class="pf-c-form pf-m-horizontal"> return html`<form class="pf-c-form pf-m-horizontal">
<ak-text-input <ak-text-input
name="name" name="name"
.value=${this.instance?.name} value=${ifDefined(this.instance?.name)}
label=${msg("Name")} label=${msg("Name")}
required required
help=${msg("Application's display Name.")} help=${msg("Application's display Name.")}
></ak-text-input> ></ak-text-input>
<ak-text-input <ak-text-input
name="slug" name="slug"
.value=${this.instance?.slug} value=${ifDefined(this.instance?.slug)}
label=${msg("Slug")} label=${msg("Slug")}
required required
help=${msg("Internal application name used in URLs.")} help=${msg("Internal application name used in URLs.")}
></ak-text-input> ></ak-text-input>
<ak-text-input <ak-text-input
name="group" name="group"
.value=${this.instance?.group} value=${ifDefined(this.instance?.group)}
label=${msg("Group")} label=${msg("Group")}
help=${msg( help=${msg(
"Optionally enter a group name. Applications with identical groups are shown grouped together.", "Optionally enter a group name. Applications with identical groups are shown grouped together.",
@ -163,7 +159,7 @@ export class ApplicationForm extends ModelForm<Application, string> {
<ak-provider-search-input <ak-provider-search-input
name="provider" name="provider"
label=${msg("Provider")} label=${msg("Provider")}
.value=${this.instance?.provider} value=${ifDefined(this.instance?.provider ?? undefined)}
help=${msg("Select a provider that this application should use.")} help=${msg("Select a provider that this application should use.")}
blankable blankable
></ak-provider-search-input> ></ak-provider-search-input>
@ -209,11 +205,11 @@ export class ApplicationForm extends ModelForm<Application, string> {
)} )}
> >
</ak-switch-input> </ak-switch-input>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) ${this.can(CapabilitiesEnum.CanSaveMedia)
? html`<ak-file-input ? html`<ak-file-input
label="${msg("Icon")}" label="${msg("Icon")}"
name="metaIcon" name="metaIcon"
.value=${this.instance?.metaIcon} value=${ifDefined(this.instance?.metaIcon ?? undefined)}
current=${msg("Currently set to:")} current=${msg("Currently set to:")}
></ak-file-input> ></ak-file-input>
${this.instance?.metaIcon ${this.instance?.metaIcon

View File

@ -7,7 +7,7 @@ import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -32,7 +32,7 @@ import {
} from "./LDAPOptionsAndHelp"; } from "./LDAPOptionsAndHelp";
@customElement("ak-application-wizard-authentication-by-ldap") @customElement("ak-application-wizard-authentication-by-ldap")
export class ApplicationWizardApplicationDetails extends BaseProviderPanel { export class ApplicationWizardApplicationDetails extends WithBrandConfig(BaseProviderPanel) {
render() { render() {
const provider = this.wizard.provider as LDAPProvider | undefined; const provider = this.wizard.provider as LDAPProvider | undefined;
const errors = this.wizard.errors.provider; const errors = this.wizard.errors.provider;
@ -57,7 +57,7 @@ export class ApplicationWizardApplicationDetails extends BaseProviderPanel {
<ak-branded-flow-search <ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication} flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow} .currentFlow=${provider?.authorizationFlow}
.brandFlow=${rootInterface()?.brand?.flowAuthentication} .brandFlow=${this.brand.flowAuthentication}
required required
></ak-branded-flow-search> ></ak-branded-flow-search>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">

View File

@ -3,7 +3,7 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-text-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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -17,7 +17,7 @@ import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/
import BaseProviderPanel from "../BaseProviderPanel"; import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-radius") @customElement("ak-application-wizard-authentication-by-radius")
export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel { export class ApplicationWizardAuthenticationByRadius extends WithBrandConfig(BaseProviderPanel) {
render() { render() {
const provider = this.wizard.provider as RadiusProvider | undefined; const provider = this.wizard.provider as RadiusProvider | undefined;
const errors = this.wizard.errors.provider; const errors = this.wizard.errors.provider;
@ -42,7 +42,7 @@ export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel {
<ak-branded-flow-search <ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication} flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow} .currentFlow=${provider?.authorizationFlow}
.brandFlow=${rootInterface()?.brand?.flowAuthentication} .brandFlow=${this.brand.flowAuthentication}
required required
></ak-branded-flow-search> ></ak-branded-flow-search>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">

View File

@ -1,8 +1,11 @@
import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/utils"; import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/utils";
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum"; 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 { 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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; 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 { ifDefined } from "lit/directives/if-defined.js";
import { import {
CapabilitiesEnum,
DeniedActionEnum, DeniedActionEnum,
Flow, Flow,
FlowDesignationEnum, FlowDesignationEnum,
@ -24,7 +26,7 @@ import {
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-flow-form") @customElement("ak-flow-form")
export class FlowForm extends ModelForm<Flow, string> { export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
async loadInstance(pk: string): Promise<Flow> { async loadInstance(pk: string): Promise<Flow> {
const flow = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({ const flow = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({
slug: pk, slug: pk,
@ -54,8 +56,8 @@ export class FlowForm extends ModelForm<Flow, string> {
flowRequest: data, flowRequest: data,
}); });
} }
const c = await config();
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) { if (this.can(CapabilitiesEnum.CanSaveMedia)) {
const icon = this.getFormFiles()["background"]; const icon = this.getFormFiles()["background"];
if (icon || this.clearBackground) { if (icon || this.clearBackground) {
await new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundCreate({ await new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundCreate({
@ -340,7 +342,7 @@ export class FlowForm extends ModelForm<Flow, string> {
</option> </option>
</select> </select>
</ak-form-element-horizontal> </ak-form-element-horizontal>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) ${this.can(CapabilitiesEnum.CanSaveMedia)
? html`<ak-form-element-horizontal ? html`<ak-form-element-horizontal
label=${msg("Background")} label=${msg("Background")}
name="background" name="background"

View File

@ -118,7 +118,7 @@ export class GroupViewPage extends AKElement {
<div class="pf-c-description-list__text"> <div class="pf-c-description-list__text">
<ak-status-label <ak-status-label
type="warning" type="warning"
?good=${this.group.isSuperuser} ?good${this.group.isSuperuser}
></ak-status-label> ></ak-status-label>
</div> </div>
</dd> </dd>

View File

@ -9,7 +9,11 @@ import { MessageLevel } from "@goauthentik/common/messages";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-status-label"; 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/ActionButton";
import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/Dropdown";
import "@goauthentik/elements/forms/DeleteBulkForm"; 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 PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { import {
CapabilitiesEnum,
CoreApi, CoreApi,
CoreUsersListTypeEnum, CoreUsersListTypeEnum,
Group, Group,
@ -107,7 +110,7 @@ export class RelatedUserAdd extends Form<{ users: number[] }> {
} }
@customElement("ak-user-related-list") @customElement("ak-user-related-list")
export class RelatedUserList extends Table<User> { export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Table<User>)) {
expandable = true; expandable = true;
checkbox = true; checkbox = true;
@ -188,8 +191,7 @@ export class RelatedUserList extends Table<User> {
row(item: User): TemplateResult[] { row(item: User): TemplateResult[] {
const canImpersonate = const canImpersonate =
rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && this.can(CapabilitiesEnum.CanImpersonate) && item.pk !== this.me?.user.pk;
item.pk !== this.me?.user.pk;
return [ return [
html`<a href="#/identity/users/${item.pk}"> html`<a href="#/identity/users/${item.pk}">
<div>${item.username}</div> <div>${item.username}</div>
@ -293,7 +295,7 @@ export class RelatedUserList extends Table<User> {
${msg("Set password")} ${msg("Set password")}
</button> </button>
</ak-forms-modal> </ak-forms-modal>
${rootInterface()?.brand?.flowRecovery ${this.brand?.flowRecovery
? html` ? html`
<ak-action-button <ak-action-button
class="pf-m-secondary" class="pf-m-secondary"

View File

@ -3,6 +3,7 @@ import { docLink } from "@goauthentik/common/global";
import { groupBy } from "@goauthentik/common/utils"; import { groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
@ -220,6 +221,8 @@ export class OutpostForm extends ModelForm<Outpost, string> {
${msg("Hold control/command to select multiple items.")} ${msg("Hold control/command to select multiple items.")}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-group aria-label="Advanced settings">
<span slot="header"> ${msg("Advanced settings")} </span>
<ak-form-element-horizontal label=${msg("Configuration")} name="config"> <ak-form-element-horizontal label=${msg("Configuration")} name="config">
<ak-codemirror <ak-codemirror
mode=${CodeMirrorMode.YAML} mode=${CodeMirrorMode.YAML}
@ -238,6 +241,7 @@ export class OutpostForm extends ModelForm<Outpost, string> {
>${msg("Documentation")}</a >${msg("Documentation")}</a
> >
</p> </p>
</ak-form-element-horizontal>`; </ak-form-element-horizontal>
</ak-form-group>`;
} }
} }

View File

@ -13,21 +13,24 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html, nothing } from "lit";
import { property } from "lit/decorators.js"; import { property, state } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.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") @customElement("ak-property-mapping-wizard-initial")
export class InitialPropertyMappingWizardPage extends WizardPage { export class InitialPropertyMappingWizardPage extends WizardPage {
@property({ attribute: false }) @property({ attribute: false })
mappingTypes: TypeCreate[] = []; mappingTypes: TypeCreate[] = [];
@property({ attribute: false })
enterprise?: LicenseSummary;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFForm, PFButton, PFRadio]; return [PFBase, PFForm, PFButton, PFRadio];
} }
@ -60,11 +63,20 @@ export class InitialPropertyMappingWizardPage extends WizardPage {
]; ];
this.host.isValid = true; this.host.isValid = true;
}} }}
?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false}
/> />
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`} <label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
>${type.name}</label >${type.name}</label
> >
<span class="pf-c-radio__description">${type.description}</span> <span class="pf-c-radio__description">${type.description}</span>
${type.requiresEnterprise && !this.enterprise?.hasLicense
? html`
<ak-alert class="pf-c-radio__description" ?inline=${true}>
${msg("Provider require enterprise.")}
<a href="#/enterprise/licenses">${msg("Learn more")}</a>
</ak-alert>
`
: nothing}
</div>`; </div>`;
})} })}
</form>`; </form>`;
@ -80,10 +92,16 @@ export class PropertyMappingWizard extends AKElement {
@property({ attribute: false }) @property({ attribute: false })
mappingTypes: TypeCreate[] = []; mappingTypes: TypeCreate[] = [];
firstUpdated(): void { @state()
new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypesList().then((types) => { enterprise?: LicenseSummary;
this.mappingTypes = types;
}); async firstUpdated(): Promise<void> {
this.mappingTypes = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsAllTypesList();
this.enterprise = await new EnterpriseApi(
DEFAULT_CONFIG,
).enterpriseLicenseSummaryRetrieve();
} }
render(): TemplateResult { render(): TemplateResult {

View File

@ -4,6 +4,7 @@ import "@goauthentik/admin/providers/proxy/ProxyProviderForm";
import "@goauthentik/admin/providers/saml/SAMLProviderForm"; import "@goauthentik/admin/providers/saml/SAMLProviderForm";
import "@goauthentik/admin/providers/saml/SAMLProviderImportForm"; import "@goauthentik/admin/providers/saml/SAMLProviderImportForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/Alert";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/forms/ProxyForm";
import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; 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 { msg, str } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html, nothing } from "lit";
import { property } from "lit/decorators.js"; import { property, state } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.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 PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.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") @customElement("ak-provider-wizard-initial")
export class InitialProviderWizardPage extends WizardPage { export class InitialProviderWizardPage extends WizardPage {
@property({ attribute: false }) @property({ attribute: false })
providerTypes: TypeCreate[] = []; providerTypes: TypeCreate[] = [];
@property({ attribute: false })
enterprise?: LicenseSummary;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFForm, PFHint, PFButton, PFRadio]; return [PFBase, PFForm, PFHint, PFButton, PFRadio];
} }
@ -79,9 +83,18 @@ export class InitialProviderWizardPage extends WizardPage {
this.host.steps = ["initial", `type-${type.component}`]; this.host.steps = ["initial", `type-${type.component}`];
this.host.isValid = true; this.host.isValid = true;
}} }}
?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false}
/> />
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label> <label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
<span class="pf-c-radio__description">${type.description}</span> <span class="pf-c-radio__description">${type.description}</span>
${type.requiresEnterprise && !this.enterprise?.hasLicense
? html`
<ak-alert class="pf-c-radio__description" ?inline=${true}>
${msg("Provider require enterprise.")}
<a href="#/enterprise/licenses">${msg("Learn more")}</a>
</ak-alert>
`
: nothing}
</div>`; </div>`;
})} })}
</form>`; </form>`;
@ -100,15 +113,19 @@ export class ProviderWizard extends AKElement {
@property({ attribute: false }) @property({ attribute: false })
providerTypes: TypeCreate[] = []; providerTypes: TypeCreate[] = [];
@state()
enterprise?: LicenseSummary;
@property({ attribute: false }) @property({ attribute: false })
finalHandler: () => Promise<void> = () => { finalHandler: () => Promise<void> = () => {
return Promise.resolve(); return Promise.resolve();
}; };
firstUpdated(): void { async firstUpdated(): Promise<void> {
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => { this.providerTypes = await new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList();
this.providerTypes = types; this.enterprise = await new EnterpriseApi(
}); DEFAULT_CONFIG,
).enterpriseLicenseSummaryRetrieve();
} }
render(): TemplateResult { render(): TemplateResult {
@ -121,7 +138,11 @@ export class ProviderWizard extends AKElement {
return this.finalHandler(); return this.finalHandler();
}} }}
> >
<ak-provider-wizard-initial slot="initial" .providerTypes=${this.providerTypes}> <ak-provider-wizard-initial
slot="initial"
.providerTypes=${this.providerTypes}
.enterprise=${this.enterprise}
>
</ak-provider-wizard-initial> </ak-provider-wizard-initial>
${this.providerTypes.map((type) => { ${this.providerTypes.map((type) => {
return html` return html`

View File

@ -3,7 +3,7 @@ import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -25,7 +25,7 @@ import {
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-provider-ldap-form") @customElement("ak-provider-ldap-form")
export class LDAPProviderFormPage extends BaseProviderForm<LDAPProvider> { export class LDAPProviderFormPage extends WithBrandConfig(BaseProviderForm<LDAPProvider>) {
async loadInstance(pk: number): Promise<LDAPProvider> { async loadInstance(pk: number): Promise<LDAPProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
id: pk, id: pk,
@ -68,7 +68,7 @@ export class LDAPProviderFormPage extends BaseProviderForm<LDAPProvider> {
<ak-branded-flow-search <ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication} flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authorizationFlow} .currentFlow=${this.instance?.authorizationFlow}
.brandFlow=${rootInterface()?.brand?.flowAuthentication} .brandFlow=${this.brand?.flowAuthentication}
required required
></ak-branded-flow-search> ></ak-branded-flow-search>
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p> <p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>

View File

@ -290,9 +290,13 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
let selected = false; let selected = false;
if (!provider?.propertyMappings) { if (!provider?.propertyMappings) {
selected = selected =
scope.managed?.startsWith( // By default select all managed scope mappings, except offline_access
(scope.managed?.startsWith(
"goauthentik.io/providers/oauth2/scope-", "goauthentik.io/providers/oauth2/scope-",
) || false; ) &&
scope.managed !==
"goauthentik.io/providers/oauth2/scope-offline_access") ||
false;
} else { } else {
selected = Array.from(provider?.propertyMappings).some((su) => { selected = Array.from(provider?.propertyMappings).some((su) => {
return su == scope.pk; return su == scope.pk;

View File

@ -106,6 +106,23 @@ export class EndpointForm extends ModelForm<Endpoint, string> {
/> />
<p class="pf-c-form__helper-text">${msg("Hostname/IP to connect to.")}</p> <p class="pf-c-form__helper-text">${msg("Hostname/IP to connect to.")}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Maximum concurrent connections")}
name="maximumConnections"
?required=${true}
>
<input
type="number"
value="${first(this.instance?.maximumConnections, 1)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Property mappings")} label=${msg("Property mappings")}
?required=${true} ?required=${true}

View File

@ -1,7 +1,7 @@
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, first, randomString } 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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
@ -14,7 +14,7 @@ import { customElement } from "lit/decorators.js";
import { FlowsInstancesListDesignationEnum, ProvidersApi, RadiusProvider } from "@goauthentik/api"; import { FlowsInstancesListDesignationEnum, ProvidersApi, RadiusProvider } from "@goauthentik/api";
@customElement("ak-provider-radius-form") @customElement("ak-provider-radius-form")
export class RadiusProviderFormPage extends BaseProviderForm<RadiusProvider> { export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm<RadiusProvider>) {
loadInstance(pk: number): Promise<RadiusProvider> { loadInstance(pk: number): Promise<RadiusProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersRadiusRetrieve({ return new ProvidersApi(DEFAULT_CONFIG).providersRadiusRetrieve({
id: pk, id: pk,
@ -57,7 +57,7 @@ export class RadiusProviderFormPage extends BaseProviderForm<RadiusProvider> {
<ak-branded-flow-search <ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication} flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authorizationFlow} .currentFlow=${this.instance?.authorizationFlow}
.brandFlow=${rootInterface()?.brand?.flowAuthentication} .brandFlow=${this.brand?.flowAuthentication}
required required
></ak-branded-flow-search> ></ak-branded-flow-search>
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p> <p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>

View File

@ -4,9 +4,12 @@ import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; 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 { ifDefined } from "lit/directives/if-defined.js";
import { import {
CapabilitiesEnum,
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
OAuthSource, OAuthSource,
OAuthSourceRequest, OAuthSourceRequest,
@ -28,7 +30,7 @@ import {
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-source-oauth-form") @customElement("ak-source-oauth-form")
export class OAuthSourceForm extends BaseSourceForm<OAuthSource> { export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuthSource>) {
async loadInstance(pk: string): Promise<OAuthSource> { async loadInstance(pk: string): Promise<OAuthSource> {
const source = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthRetrieve({ const source = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthRetrieve({
slug: pk, slug: pk,
@ -318,7 +320,7 @@ export class OAuthSourceForm extends BaseSourceForm<OAuthSource> {
/> />
<p class="pf-c-form__helper-text">${placeholderHelperText}</p> <p class="pf-c-form__helper-text">${placeholderHelperText}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) ${this.can(CapabilitiesEnum.CanSaveMedia)
? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon"> ? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon">
<input type="file" value="" class="pf-c-form-control" /> <input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon ${this.instance?.icon

View File

@ -2,10 +2,13 @@ import "@goauthentik/admin/common/ak-flow-search/ak-source-flow-search";
import { iconHelperText, placeholderHelperText } from "@goauthentik/admin/helperText"; import { iconHelperText, placeholderHelperText } from "@goauthentik/admin/helperText";
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; 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 { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; 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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; 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 { ifDefined } from "lit/directives/if-defined.js";
import { import {
CapabilitiesEnum,
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
PlexSource, PlexSource,
SourcesApi, SourcesApi,
@ -24,7 +26,7 @@ import {
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-source-plex-form") @customElement("ak-source-plex-form")
export class PlexSourceForm extends BaseSourceForm<PlexSource> { export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSource>) {
async loadInstance(pk: string): Promise<PlexSource> { async loadInstance(pk: string): Promise<PlexSource> {
const source = await new SourcesApi(DEFAULT_CONFIG).sourcesPlexRetrieve({ const source = await new SourcesApi(DEFAULT_CONFIG).sourcesPlexRetrieve({
slug: pk, slug: pk,
@ -63,8 +65,7 @@ export class PlexSourceForm extends BaseSourceForm<PlexSource> {
plexSourceRequest: data, plexSourceRequest: data,
}); });
} }
const c = await config(); if (this.can(CapabilitiesEnum.CanSaveMedia)) {
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
const icon = this.getFormFiles()["icon"]; const icon = this.getFormFiles()["icon"];
if (icon || this.clearIcon) { if (icon || this.clearIcon) {
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({ await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
@ -255,7 +256,7 @@ export class PlexSourceForm extends BaseSourceForm<PlexSource> {
/> />
<p class="pf-c-form__helper-text">${placeholderHelperText}</p> <p class="pf-c-form__helper-text">${placeholderHelperText}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) ${this.can(CapabilitiesEnum.CanSaveMedia)
? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon"> ? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon">
<input type="file" value="" class="pf-c-form-control" /> <input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon ${this.instance?.icon

View File

@ -5,7 +5,10 @@ import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; 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/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -18,7 +21,6 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { import {
BindingTypeEnum, BindingTypeEnum,
CapabilitiesEnum,
DigestAlgorithmEnum, DigestAlgorithmEnum,
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
NameIdPolicyEnum, NameIdPolicyEnum,
@ -29,7 +31,7 @@ import {
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-source-saml-form") @customElement("ak-source-saml-form")
export class SAMLSourceForm extends BaseSourceForm<SAMLSource> { export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSource>) {
@state() @state()
clearIcon = false; clearIcon = false;
@ -149,7 +151,7 @@ export class SAMLSourceForm extends BaseSourceForm<SAMLSource> {
</option> </option>
</select> </select>
</ak-form-element-horizontal> </ak-form-element-horizontal>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) ${this.can(CapabilitiesEnum.CanSaveMedia)
? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon"> ? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon">
<input type="file" value="" class="pf-c-form-control" /> <input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon ${this.instance?.icon

View File

@ -12,6 +12,11 @@ import { DefaultUIConfig, uiConfig } from "@goauthentik/common/ui/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-status-label"; import "@goauthentik/components/ak-status-label";
import { rootInterface } from "@goauthentik/elements/Base"; 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 { PFSize } from "@goauthentik/elements/Spinner";
import "@goauthentik/elements/TreeView"; import "@goauthentik/elements/TreeView";
import "@goauthentik/elements/buttons/ActionButton"; 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 PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { import { CoreApi, ResponseError, SessionUser, User, UserPath } from "@goauthentik/api";
CapabilitiesEnum,
CoreApi,
ResponseError,
SessionUser,
User,
UserPath,
} from "@goauthentik/api";
export const requestRecoveryLink = (user: User) => export const requestRecoveryLink = (user: User) =>
new CoreApi(DEFAULT_CONFIG) new CoreApi(DEFAULT_CONFIG)
@ -93,7 +91,7 @@ const recoveryButtonStyles = css`
`; `;
@customElement("ak-user-list") @customElement("ak-user-list")
export class UserListPage extends TablePage<User> { export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePage<User>)) {
expandable = true; expandable = true;
checkbox = true; checkbox = true;
@ -244,8 +242,7 @@ export class UserListPage extends TablePage<User> {
row(item: User): TemplateResult[] { row(item: User): TemplateResult[] {
const canImpersonate = const canImpersonate =
rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && this.can(CapabilitiesEnum.CanImpersonate) && item.pk !== this.me?.user.pk;
item.pk !== this.me?.user.pk;
return [ return [
html`<a href="#/identity/users/${item.pk}"> html`<a href="#/identity/users/${item.pk}">
<div>${item.username}</div> <div>${item.username}</div>
@ -355,7 +352,7 @@ export class UserListPage extends TablePage<User> {
${msg("Set password")} ${msg("Set password")}
</button> </button>
</ak-forms-modal> </ak-forms-modal>
${rootInterface()?.brand?.flowRecovery ${this.brand.flowRecovery
? html` ? html`
<ak-action-button <ak-action-button
class="pf-m-secondary" class="pf-m-secondary"

View File

@ -22,8 +22,9 @@ import {
import "@goauthentik/components/ak-status-label"; import "@goauthentik/components/ak-status-label";
import "@goauthentik/components/events/ObjectChangelog"; import "@goauthentik/components/events/ObjectChangelog";
import "@goauthentik/components/events/UserEvents"; import "@goauthentik/components/events/UserEvents";
import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import { WithCapabilitiesConfig } from "@goauthentik/elements/Interface/capabilitiesProvider";
import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/PageHeader";
import { PFSize } from "@goauthentik/elements/Spinner"; import { PFSize } from "@goauthentik/elements/Spinner";
import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/Tabs";
@ -60,7 +61,7 @@ import {
import "./UserDevicesTable"; import "./UserDevicesTable";
@customElement("ak-user-view") @customElement("ak-user-view")
export class UserViewPage extends AKElement { export class UserViewPage extends WithCapabilitiesConfig(AKElement) {
@property({ type: Number }) @property({ type: Number })
set userId(id: number) { set userId(id: number) {
me().then((me) => { me().then((me) => {
@ -163,8 +164,7 @@ export class UserViewPage extends AKElement {
renderActionButtons(user: User) { renderActionButtons(user: User) {
const canImpersonate = const canImpersonate =
rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && this.can(CapabilitiesEnum.CanImpersonate) && user.pk !== this.me?.user.pk;
user.pk !== this.me?.user.pk;
return html`<div class="ak-button-collection"> return html`<div class="ak-button-collection">
<ak-forms-modal> <ak-forms-modal>

View File

@ -1,7 +1,9 @@
import { createContext } from "@lit-labs/context"; import { createContext } from "@lit-labs/context";
import { type Config } from "@goauthentik/api"; import type { Config, CurrentBrand } from "@goauthentik/api";
export const authentikConfigContext = createContext<Config>(Symbol("authentik-config-context")); export const authentikConfigContext = createContext<Config>(Symbol("authentik-config-context"));
export const authentikBrandContext = createContext<CurrentBrand>(Symbol("authentik-brand-context"));
export default authentikConfigContext; export default authentikConfigContext;

View File

@ -1,20 +1,18 @@
import { brand, config } from "@goauthentik/common/api/config";
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; 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 { 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 { localized } from "@lit/localize";
import { CSSResult, LitElement } from "lit"; import { LitElement } from "lit";
import { state } from "lit/decorators.js";
import AKGlobal from "@goauthentik/common/styles/authentik.css"; import AKGlobal from "@goauthentik/common/styles/authentik.css";
import ThemeDark from "@goauthentik/common/styles/theme-dark.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 { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
import { AdoptedStyleSheetsElement } from "./types";
type AkInterface = HTMLElement & { type AkInterface = HTMLElement & {
getTheme: () => Promise<UiThemeEnum>; getTheme: () => Promise<UiThemeEnum>;
brand?: CurrentBrand; brand?: CurrentBrand;
@ -25,13 +23,6 @@ type AkInterface = HTMLElement & {
export const rootInterface = <T extends AkInterface>(): T | undefined => export const rootInterface = <T extends AkInterface>(): T | undefined =>
(document.body.querySelector("[data-ak-interface-root]") as 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<string[]> | undefined; let css: Promise<string[]> | undefined;
function fetchCustomCSS(): Promise<string[]> { function fetchCustomCSS(): Promise<string[]> {
if (!css) { if (!css) {
@ -52,10 +43,6 @@ function fetchCustomCSS(): Promise<string[]> {
return css; return css;
} }
export interface AdoptedStyleSheetsElement {
adoptedStyleSheets: readonly CSSStyleSheet[];
}
const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)"; const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)";
@localized() @localized()
@ -175,49 +162,3 @@ export class AKElement extends LitElement {
this.requestUpdate(); 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<UiThemeEnum> {
if (!this.uiConfig) {
this.uiConfig = await uiConfig();
}
return this.uiConfig.theme?.base || UiThemeEnum.Automatic;
}
}

View File

@ -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<UiThemeEnum>;
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<UiThemeEnum> {
if (!this.uiConfig) {
this.uiConfig = await uiConfig();
}
return this.uiConfig.theme?.base || UiThemeEnum.Automatic;
}
}

View File

@ -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<T = object> = new (...args: any[]) => T;
export function WithAuthentikConfig<T extends Constructor<LitElement>>(
superclass: T,
subscribe = true,
) {
abstract class WithAkConfigProvider extends superclass {
@consume({ context: authentikConfigContext, subscribe })
public authentikConfig!: Config;
}
return WithAkConfigProvider;
}

View File

@ -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<T = object> = abstract new (...args: any[]) => T;
export function WithBrandConfig<T extends Constructor<LitElement>>(
superclass: T,
subscribe = true,
) {
abstract class WithBrandProvider extends superclass {
@consume({ context: authentikBrandContext, subscribe })
public brand!: CurrentBrand;
}
return WithBrandProvider;
}

View File

@ -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<T = object> = 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<T extends Constructor<LitElement>>(
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 };

View File

@ -0,0 +1,4 @@
import { Interface } from "./Interface";
export { Interface };
export default Interface;

View File

@ -8,7 +8,8 @@ import {
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { currentInterface } from "@goauthentik/common/sentry"; import { currentInterface } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users"; 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 "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -23,7 +24,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { EventsApi } from "@goauthentik/api"; import { EventsApi } from "@goauthentik/api";
@customElement("ak-page-header") @customElement("ak-page-header")
export class PageHeader extends AKElement { export class PageHeader extends WithBrandConfig(AKElement) {
@property() @property()
icon?: string; icon?: string;
@ -35,9 +36,8 @@ export class PageHeader extends AKElement {
@property() @property()
set header(value: string) { set header(value: string) {
const brand = rootInterface()?.brand;
const currentIf = currentInterface(); const currentIf = currentInterface();
let title = brand?.brandingTitle || TITLE_DEFAULT; let title = this.brand?.brandingTitle || TITLE_DEFAULT;
if (currentIf === "admin") { if (currentIf === "admin") {
title = `${msg("Admin")} - ${title}`; title = `${msg("Admin")} - ${title}`;
} }

View File

@ -46,6 +46,8 @@ const LOCALE_TABLE: LocaleRow[] = [
["es", /^es([_-]|$)/i, () => msg("Spanish"), async () => await import("@goauthentik/locales/es")], ["es", /^es([_-]|$)/i, () => msg("Spanish"), async () => await import("@goauthentik/locales/es")],
["de", /^de([_-]|$)/i, () => msg("German"), async () => await import("@goauthentik/locales/de")], ["de", /^de([_-]|$)/i, () => msg("German"), async () => await import("@goauthentik/locales/de")],
["fr", /^fr([_-]|$)/i, () => msg("French"), async () => await import("@goauthentik/locales/fr")], ["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")], ["pl", /^pl([_-]|$)/i, () => msg("Polish"), async () => await import("@goauthentik/locales/pl")],
["tr", /^tr([_-]|$)/i, () => msg("Turkish"), async () => await import("@goauthentik/locales/tr")], ["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")], ["zh-Hant", /^zh[_-](HK|Hant)/i, () => msg("Chinese (traditional)"), async () => await import("@goauthentik/locales/zh-Hant")],

View File

@ -21,10 +21,8 @@ export class EnterpriseStatusBanner extends AKElement {
return [PFBanner]; return [PFBanner];
} }
firstUpdated(): void { async firstUpdated(): Promise<void> {
new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((b) => { this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve();
this.summary = b;
});
} }
renderBanner(): TemplateResult { renderBanner(): TemplateResult {

View File

@ -1,6 +1,6 @@
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants"; import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
import { first } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base";
import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
@ -27,7 +27,7 @@ export const DefaultBrand: CurrentBrand = {
}; };
@customElement("ak-sidebar-brand") @customElement("ak-sidebar-brand")
export class SidebarBrand extends AKElement { export class SidebarBrand extends WithBrandConfig(AKElement) {
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
PFBase, PFBase,
@ -85,10 +85,7 @@ export class SidebarBrand extends AKElement {
<a href="#/" class="pf-c-page__header-brand-link"> <a href="#/" class="pf-c-page__header-brand-link">
<div class="pf-c-brand ak-brand"> <div class="pf-c-brand ak-brand">
<img <img
src="${first( src=${this.brand?.brandingLogo ?? DefaultBrand.brandingLogo}
rootInterface()?.brand?.brandingLogo,
DefaultBrand.brandingLogo,
)}"
alt="authentik Logo" alt="authentik Logo"
loading="lazy" loading="lazy"
/> />

View File

@ -0,0 +1,3 @@
export interface AdoptedStyleSheetsElement {
adoptedStyleSheets: readonly CSSStyleSheet[];
}

View File

@ -0,0 +1,4 @@
import { CSSResult } from "lit";
export const ensureCSSStyleSheet = (css: CSSStyleSheet | CSSResult): CSSStyleSheet =>
css instanceof CSSResult ? css.styleSheet! : css;

View File

@ -1,5 +1,5 @@
import { TITLE_DEFAULT } from "@goauthentik/app/common/constants"; 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 "@goauthentik/elements/LoadingOverlay";
import Guacamole from "guacamole-common-js"; import Guacamole from "guacamole-common-js";

View File

@ -8,7 +8,7 @@ import { globalAK } from "@goauthentik/common/global";
import { configureSentry } from "@goauthentik/common/sentry"; import { configureSentry } from "@goauthentik/common/sentry";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { WebsocketClient } from "@goauthentik/common/ws"; 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/LoadingOverlay";
import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/ak-locale-context";
import "@goauthentik/flow/sources/apple/AppleLoginInit"; import "@goauthentik/flow/sources/apple/AppleLoginInit";

View File

@ -89,6 +89,9 @@ export class AuthenticatorValidateStage
display: flex; display: flex;
align-items: center; align-items: center;
} }
:host([theme="dark"]) .authenticator-button {
color: var(--ak-dark-foreground) !important;
}
i { i {
font-size: 1.5rem; font-size: 1.5rem;
padding: 1rem 0; padding: 1rem 0;

View File

@ -1,6 +1,9 @@
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Divider"; import "@goauthentik/elements/Divider";
import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/EmptyState";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
import { LOCALES } from "@goauthentik/elements/ak-locale-context/definitions"; import { LOCALES } from "@goauthentik/elements/ak-locale-context/definitions";
import "@goauthentik/elements/forms/FormElement"; import "@goauthentik/elements/forms/FormElement";
import { BaseStage } from "@goauthentik/flow/stages/base"; 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 PFBase from "@patternfly/patternfly/patternfly-base.css";
import { import {
CapabilitiesEnum,
PromptChallenge, PromptChallenge,
PromptChallengeResponseRequest, PromptChallengeResponseRequest,
PromptTypeEnum, PromptTypeEnum,
@ -28,7 +30,9 @@ import {
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-stage-prompt") @customElement("ak-stage-prompt")
export class PromptStage extends BaseStage<PromptChallenge, PromptChallengeResponseRequest> { export class PromptStage extends WithCapabilitiesConfig(
BaseStage<PromptChallenge, PromptChallengeResponseRequest>,
) {
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
PFBase, PFBase,
@ -193,10 +197,7 @@ ${prompt.initialValue}</textarea
</div> `; </div> `;
})}`; })}`;
case PromptTypeEnum.AkLocale: { case PromptTypeEnum.AkLocale: {
const inDebug = rootInterface()?.config?.capabilities.includes( const locales = this.can(CapabilitiesEnum.CanDebug)
CapabilitiesEnum.CanDebug,
);
const locales = inDebug
? LOCALES ? LOCALES
: LOCALES.filter((locale) => locale.code !== "debug"); : LOCALES.filter((locale) => locale.code !== "debug");
const options = locales.map( const options = locales.map(

View File

@ -15,10 +15,13 @@ export const targetLocales = [
`en`, `en`,
`es`, `es`,
`fr`, `fr`,
`ko`,
`nl`,
`pl`, `pl`,
`pseudo-LOCALE`, `pseudo-LOCALE`,
`tr`, `tr`,
`zh_TW`, `zh_TW`,
`zh-CN`,
`zh-Hans`, `zh-Hans`,
`zh-Hant`, `zh-Hant`,
] as const; ] as const;
@ -32,10 +35,13 @@ export const allLocales = [
`en`, `en`,
`es`, `es`,
`fr`, `fr`,
`ko`,
`nl`,
`pl`, `pl`,
`pseudo-LOCALE`, `pseudo-LOCALE`,
`tr`, `tr`,
`zh_TW`, `zh_TW`,
`zh-CN`,
`zh-Hans`, `zh-Hans`,
`zh-Hant`, `zh-Hant`,
] as const; ] as const;

View File

@ -2,7 +2,7 @@ import { CSRFHeaderName } from "@goauthentik/common/api/middleware";
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { globalAK } from "@goauthentik/common/global";
import { first, getCookie } from "@goauthentik/common/utils"; 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 "@goauthentik/elements/ak-locale-context";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import "rapidoc"; import "rapidoc";

View File

@ -1,5 +1,5 @@
import { globalAK } from "@goauthentik/common/global"; import { globalAK } from "@goauthentik/common/global";
import { Interface } from "@goauthentik/elements/Base"; import { Interface } from "@goauthentik/elements/Interface";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";

View File

@ -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"; import { customElement, property } from "lit/decorators.js";

View File

@ -9,7 +9,7 @@ import { UserDisplay } from "@goauthentik/common/ui/config";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { WebsocketClient } from "@goauthentik/common/ws"; 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/ak-locale-context";
import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";

View File

@ -2,14 +2,15 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { refreshMe } from "@goauthentik/common/users"; 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 { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { StageHost } from "@goauthentik/flow/stages/base"; import { StageHost } from "@goauthentik/flow/stages/base";
import "@goauthentik/user/user-settings/details/stages/prompt/PromptStage"; import "@goauthentik/user/user-settings/details/stages/prompt/PromptStage";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit"; 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 { unsafeHTML } from "lit/directives/unsafe-html.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -21,7 +22,6 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { import {
ChallengeChoices, ChallengeChoices,
ChallengeTypes, ChallengeTypes,
CurrentBrand,
FlowChallengeResponseRequest, FlowChallengeResponseRequest,
FlowErrorChallenge, FlowErrorChallenge,
FlowsApi, FlowsApi,
@ -31,13 +31,19 @@ import {
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-user-settings-flow-executor") @customElement("ak-user-settings-flow-executor")
export class UserSettingsFlowExecutor extends AKElement implements StageHost { export class UserSettingsFlowExecutor
extends WithBrandConfig(AKElement, true)
implements StageHost
{
@property() @property()
flowSlug?: string; flowSlug?: string;
<<<<<<< HEAD
@state() @state()
brand?: CurrentBrand; brand?: CurrentBrand;
=======
>>>>>>> main
private _challenge?: ChallengeTypes; private _challenge?: ChallengeTypes;
@property({ attribute: false }) @property({ attribute: false })
@ -87,7 +93,6 @@ export class UserSettingsFlowExecutor extends AKElement implements StageHost {
} }
firstUpdated(): void { firstUpdated(): void {
this.brand = rootInterface()?.brand;
this.flowSlug = this.brand?.flowUserSettings; this.flowSlug = this.brand?.flowUserSettings;
if (!this.flowSlug) { if (!this.flowSlug) {
return; return;

View File

@ -6192,6 +6192,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -6222,6 +6234,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -6467,6 +6467,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -6497,6 +6509,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -6108,6 +6108,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -6138,6 +6150,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -8143,6 +8143,18 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
<target>Détermine combien de temps une session dure avant déconnexion et ré-authorisation.</target> <target>Détermine combien de temps une session dure avant déconnexion et ré-authorisation.</target>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -8173,6 +8185,12 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2"> <?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="ko" source-language="en" original="lit-localize-inputs" datatype="plaintext"> <file target-language="ko" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body> <body>
<trans-unit id="s4caed5b7a7e5d89b"> <trans-unit id="s4caed5b7a7e5d89b">
@ -443,11 +443,6 @@
<source>Client IP</source> <source>Client IP</source>
<target>클라이언트 IP</target> <target>클라이언트 IP</target>
</trans-unit>
<trans-unit id="s45f9e7ce0897f9e5">
<source>Tenant</source>
<target>테넌트</target>
</trans-unit> </trans-unit>
<trans-unit id="s2152f3482784705f"> <trans-unit id="s2152f3482784705f">
<source>Recent events</source> <source>Recent events</source>
@ -612,8 +607,8 @@
</trans-unit> </trans-unit>
<trans-unit id="saa0e2675da69651b"> <trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source> <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; 을 찾을 수 없습니다.</target> <target>URL "<x id="0" equiv-text="${this.url}"/>" 을 찾을 수 없습니다.</target>
</trans-unit> </trans-unit>
<trans-unit id="s58cd9c2fe836d9c6"> <trans-unit id="s58cd9c2fe836d9c6">
@ -1054,8 +1049,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa8384c9c26731f83"> <trans-unit id="sa8384c9c26731f83">
<source>To allow any redirect URI, set this value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source> <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
<target>리디렉션 URI를 허용하려면 이 값을 &quot;.*&quot;로 설정합니다. 이로 인해 발생할 수 있는 보안상의 영향에 유의하세요.</target> <target>리디렉션 URI를 허용하려면 이 값을 ".*"로 설정합니다. 이로 인해 발생할 수 있는 보안상의 영향에 유의하세요.</target>
</trans-unit> </trans-unit>
<trans-unit id="s55787f4dfcdce52b"> <trans-unit id="s55787f4dfcdce52b">
@ -1792,8 +1787,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa90b7809586c35ce"> <trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source> <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>전체 URL, 상대 경로를 입력하거나, 또는 'fa://fa-test'를 사용하여 Font Awesome 아이콘 &quot;fa-test&quot;를 사용합니다.</target> <target>전체 URL, 상대 경로를 입력하거나, 또는 'fa://fa-test'를 사용하여 Font Awesome 아이콘 "fa-test"를 사용합니다.</target>
</trans-unit> </trans-unit>
<trans-unit id="s0410779cb47de312"> <trans-unit id="s0410779cb47de312">
@ -2972,7 +2967,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s76768bebabb7d543"> <trans-unit id="s76768bebabb7d543">
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> <source>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,...'</source>
<target>그룹 구성원이 포함된 필드입니다. 'memberUid' 필드를 사용하는 경우 값에 상대적인 고유 이름이 포함된 것으로 가정합니다 (예:'memberUid=some-user' 대신 'memberUid=cn=some-user,ou=groups,...').</target> <target>그룹 구성원이 포함된 필드입니다. 'memberUid' 필드를 사용하는 경우 값에 상대적인 고유 이름이 포함된 것으로 가정합니다 (예:'memberUid=some-user' 대신 'memberUid=cn=some-user,ou=groups,...').</target>
</trans-unit> </trans-unit>
@ -3617,16 +3612,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Update Token</source> <source>Update Token</source>
<target>토큰 업데이트</target> <target>토큰 업데이트</target>
</trans-unit>
<trans-unit id="s0af6301e76e2a2a5">
<source>Successfully updated tenant.</source>
<target>테넌트를 성공적으로 업데이트했습니다.</target>
</trans-unit>
<trans-unit id="sf55c7c06dbc2c8c6">
<source>Successfully created tenant.</source>
<target>테넌트를 성공적으로 만들었습니다.</target>
</trans-unit> </trans-unit>
<trans-unit id="s41706a202b6c40f1"> <trans-unit id="s41706a202b6c40f1">
<source>Domain</source> <source>Domain</source>
@ -3642,11 +3627,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Default</source> <source>Default</source>
<target>기본</target> <target>기본</target>
</trans-unit>
<trans-unit id="se1c85959463f53df">
<source>Use this tenant for each domain that doesn't have a dedicated tenant.</source>
<target>전용 테넌트가 없는 각 도메인에 이 테넌트를 사용하세요.</target>
</trans-unit> </trans-unit>
<trans-unit id="sc19838ca8c135c1b"> <trans-unit id="sc19838ca8c135c1b">
<source>Branding settings</source> <source>Branding settings</source>
@ -3764,29 +3744,14 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s7b1fba26d245cb1c"> <trans-unit id="s7b1fba26d245cb1c">
<source>When using an external logging solution for archiving, this can be set to &quot;minutes=5&quot;.</source> <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
<target>아카이브에 외부 로깅 솔루션을 사용하는 경우, 이 값을 &quot;minutes=5&quot;로 설정할 수 있습니다.</target> <target>아카이브에 외부 로깅 솔루션을 사용하는 경우, 이 값을 "minutes=5"로 설정할 수 있습니다.</target>
</trans-unit> </trans-unit>
<trans-unit id="s44536d20bb5c8257"> <trans-unit id="s44536d20bb5c8257">
<source>This setting only affects new Events, as the expiration is saved per-event.</source> <source>This setting only affects new Events, as the expiration is saved per-event.</source>
<target>T만료는 이벤트별로 저장되므로 설정은 새 이벤트에만 영향을 줍니다.</target> <target>T만료는 이벤트별로 저장되므로 설정은 새 이벤트에만 영향을 줍니다.</target>
</trans-unit>
<trans-unit id="s3bb51cabb02b997e">
<source>Format: &quot;weeks=3;days=2;hours=3,seconds=2&quot;.</source>
<target>서식: &quot;weeks=3;days=2;hours=3,seconds=2&quot;.</target>
</trans-unit>
<trans-unit id="s04bfd02201db5ab8">
<source>Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this tenant.</source>
<target>YAML 또는 JSON을 사용하여 사용자 지정 속성을 설정합니다. 이 테넌트가 요청을 처리하는 경우 여기에서 설정한 모든 속성은 사용자가 상속받게 됩니다.</target>
</trans-unit>
<trans-unit id="s7f9e79189a3d19e2">
<source>Tenants</source>
<target>테넌트</target>
</trans-unit> </trans-unit>
<trans-unit id="s164be9a7537b99f6"> <trans-unit id="s164be9a7537b99f6">
<source>Configure visual settings and defaults for different domains.</source> <source>Configure visual settings and defaults for different domains.</source>
@ -3797,21 +3762,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Default?</source> <source>Default?</source>
<target>기본값?</target> <target>기본값?</target>
</trans-unit>
<trans-unit id="s69a56a3022c4be7f">
<source>Tenant(s)</source>
<target>테넌트</target>
</trans-unit>
<trans-unit id="s1b606acd76ba2c4c">
<source>Update Tenant</source>
<target>테넌트 업데이트</target>
</trans-unit>
<trans-unit id="s773aa6621d7e37b7">
<source>Create Tenant</source>
<target>테넌트 생성</target>
</trans-unit> </trans-unit>
<trans-unit id="s8cb7bb82e96d5d77"> <trans-unit id="s8cb7bb82e96d5d77">
<source>Policies</source> <source>Policies</source>
@ -3967,8 +3917,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sa95a538bfbb86111"> <trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source> <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>정말 <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot; 을(를) 업데이트 하시겠습니까?</target> <target>정말 <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>" 을(를) 업데이트 하시겠습니까?</target>
</trans-unit> </trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6"> <trans-unit id="sc92d7cfb6ee1fec6">
@ -4081,11 +4031,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Recovery link cannot be emailed, user has no email address saved.</source> <source>Recovery link cannot be emailed, user has no email address saved.</source>
<target>복구 링크를 이메일로 보낼 수 없습니다. 사용자가 저장한 이메일 주소가 없습니다.</target> <target>복구 링크를 이메일로 보낼 수 없습니다. 사용자가 저장한 이메일 주소가 없습니다.</target>
</trans-unit>
<trans-unit id="s63d89a6ae0969c30">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active tenant.</source>
<target>사용자가 직접 비밀번호를 재설정할 수 있도록 하려면 현재 활성 상태인 테넌트에서 복구 플로우를 구성하세요.</target>
</trans-unit> </trans-unit>
<trans-unit id="s720594461542943f"> <trans-unit id="s720594461542943f">
<source>Add User</source> <source>Add User</source>
@ -5052,8 +4997,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sdf1d8edef27236f0"> <trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source> <source>A "roaming" authenticator, like a YubiKey</source>
<target>YubiKey 같은 &quot;로밍&quot; 인증기</target> <target>YubiKey 같은 "로밍" 인증기</target>
</trans-unit> </trans-unit>
<trans-unit id="sfffba7b23d8fb40c"> <trans-unit id="sfffba7b23d8fb40c">
@ -5387,8 +5332,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s2d5f69929bb7221d"> <trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${prompt.name}"/> (&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${prompt.type}"/>)</source> <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
<target><x id="0" equiv-text="${prompt.name}"/> (&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${prompt.type}"/>)</target> <target><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</target>
</trans-unit> </trans-unit>
<trans-unit id="s3b7b519444181264"> <trans-unit id="s3b7b519444181264">
@ -5436,7 +5381,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s1608b2f94fa0dbd4"> <trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source> <source>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.</source>
<target>기간을 0 이상으로 설정하면, 사용자에게 '로그인 상태 유지'를 선택할 수 있는 옵션이 제공되며, 이 경우 세션이 여기에 지정된 시간만큼 연장됩니다.</target> <target>기간을 0 이상으로 설정하면, 사용자에게 '로그인 상태 유지'를 선택할 수 있는 옵션이 제공되며, 이 경우 세션이 여기에 지정된 시간만큼 연장됩니다.</target>
</trans-unit> </trans-unit>
@ -7813,10 +7758,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>A copy of this recovery link has been placed in your clipboard</source> <source>A copy of this recovery link has been placed in your clipboard</source>
<target>이 복구 링크의 사본이 클립보드에 저장되었습니다.</target> <target>이 복구 링크의 사본이 클립보드에 저장되었습니다.</target>
</trans-unit> </trans-unit>
<trans-unit id="s5b8ee296ed258568">
<source>The current tenant must have a recovery flow configured to use a recovery link</source>
<target>현재 테넌트에 복구 링크를 사용하도록 구성된 복구 플로우가 있어야 합니다.</target>
</trans-unit>
<trans-unit id="s895514dda9cb9c94"> <trans-unit id="s895514dda9cb9c94">
<source>Create recovery link</source> <source>Create recovery link</source>
<target>복구 링크 생성</target> <target>복구 링크 생성</target>
@ -7926,8 +7867,8 @@ Bindings to groups/users are checked against the user of the event.</source>
<target>사용자 생성과 <x id="0" equiv-text="${this.group.name}"/> 그룹 추가에 성공했습니다.</target> <target>사용자 생성과 <x id="0" equiv-text="${this.group.name}"/> 그룹 추가에 성공했습니다.</target>
</trans-unit> </trans-unit>
<trans-unit id="s824e0943a7104668"> <trans-unit id="s824e0943a7104668">
<source>This user will be added to the group &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;.</source> <source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
<target>이 사용자는 &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot; 그룹에 추가됩니다.</target> <target>이 사용자는 "<x id="0" equiv-text="${this.targetGroup.name}"/>" 그룹에 추가됩니다.</target>
</trans-unit> </trans-unit>
<trans-unit id="s62e7f6ed7d9cb3ca"> <trans-unit id="s62e7f6ed7d9cb3ca">
<source>Pretend user exists</source> <source>Pretend user exists</source>
@ -8004,6 +7945,260 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s047a5f0211fedc72"> <trans-unit id="s047a5f0211fedc72">
<source>Require Outpost (flow can only be executed from an outpost).</source> <source>Require Outpost (flow can only be executed from an outpost).</source>
<target>Outpost필요 (플로우는 Outpost에서만 실행할 수 있음).</target> <target>Outpost필요 (플로우는 Outpost에서만 실행할 수 있음).</target>
</trans-unit>
<trans-unit id="s744401846fea6e76">
<source>Brand</source>
</trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sa2ea0fcd3ffa80e0">
<source>Connection expiry</source>
</trans-unit>
<trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit>
<trans-unit id="s3271da6c18c25b18">
<source>Connection settings.</source>
</trans-unit>
<trans-unit id="s2f4ca2148183d692">
<source>Successfully updated endpoint.</source>
</trans-unit>
<trans-unit id="s5adee855dbe191d9">
<source>Successfully created endpoint.</source>
</trans-unit>
<trans-unit id="s61e136c0658e27d5">
<source>Protocol</source>
</trans-unit>
<trans-unit id="sa062b019ff0c8809">
<source>RDP</source>
</trans-unit>
<trans-unit id="s97f9bf19fa5b57d1">
<source>SSH</source>
</trans-unit>
<trans-unit id="s7c100119e9ffcc32">
<source>VNC</source>
</trans-unit>
<trans-unit id="s6b05f9d8801fc14f">
<source>Host</source>
</trans-unit>
<trans-unit id="sb474f652a2c2fc76">
<source>Hostname/IP to connect to.</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s8276649077e8715c">
<source>Endpoint(s)</source>
</trans-unit>
<trans-unit id="sf1dabfe0fe8a75ad">
<source>Update Endpoint</source>
</trans-unit>
<trans-unit id="s008496c7716b9812">
<source>These bindings control which users will have access to this endpoint. Users must also have access to the application.</source>
</trans-unit>
<trans-unit id="s38e7cd1a24e70faa">
<source>Create Endpoint</source>
</trans-unit>
<trans-unit id="s4770c10e5b1c028c">
<source>RAC is in preview.</source>
</trans-unit>
<trans-unit id="s168565f5ac74a89f">
<source>Update RAC Provider</source>
</trans-unit>
<trans-unit id="s8465a2caa2d9ea5d">
<source>Endpoints</source>
</trans-unit>
<trans-unit id="s9857d883d8eb98fc">
<source>General settings</source>
</trans-unit>
<trans-unit id="sd2066881798a1b96">
<source>RDP settings</source>
</trans-unit>
<trans-unit id="sb864dc36a463a155">
<source>Ignore server certificate</source>
</trans-unit>
<trans-unit id="s20366a8d1eaaca54">
<source>Enable wallpaper</source>
</trans-unit>
<trans-unit id="s1e44c5350ef7598c">
<source>Enable font-smoothing</source>
</trans-unit>
<trans-unit id="s04ff5d6ae711e6d6">
<source>Enable full window dragging</source>
</trans-unit>
<trans-unit id="sab21e1f62676b56c">
<source>Successfully updated brand.</source>
</trans-unit>
<trans-unit id="sa43e43fd3a23e22d">
<source>Successfully created brand.</source>
</trans-unit>
<trans-unit id="s41b3f9b4c98aabd9">
<source>Use this brand for each domain that doesn't have a dedicated brand.</source>
</trans-unit>
<trans-unit id="s17260b71484b307f">
<source>Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this brand.</source>
</trans-unit>
<trans-unit id="s79fc990a2b58f27f">
<source>Brands</source>
</trans-unit>
<trans-unit id="s02774bc46a167346">
<source>Brand(s)</source>
</trans-unit>
<trans-unit id="s801bf3d03f4a3ff1">
<source>Update Brand</source>
</trans-unit>
<trans-unit id="s5c3efec5330e0000">
<source>Create Brand</source>
</trans-unit>
<trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit>
<trans-unit id="s663ccbfdf27e8dd0">
<source>Network binding</source>
</trans-unit>
<trans-unit id="sb108a06693c67753">
<source>No binding</source>
</trans-unit>
<trans-unit id="s5aab90c74f1233b8">
<source>Bind ASN</source>
</trans-unit>
<trans-unit id="s488303b048afe83b">
<source>Bind ASN and Network</source>
</trans-unit>
<trans-unit id="s3268dcfe0c8234dc">
<source>Bind ASN, Network and IP</source>
</trans-unit>
<trans-unit id="s226381aca231644f">
<source>Configure if sessions created by this stage should be bound to the Networks they were created in.</source>
</trans-unit>
<trans-unit id="s2555a1f20f3fd93e">
<source>GeoIP binding</source>
</trans-unit>
<trans-unit id="s3d63c78f93c9a92e">
<source>Bind Continent</source>
</trans-unit>
<trans-unit id="s395d5863b3a259b5">
<source>Bind Continent and Country</source>
</trans-unit>
<trans-unit id="s625ea0c32b4b136c">
<source>Bind Continent, Country and City</source>
</trans-unit>
<trans-unit id="s4bc7a1a88961be90">
<source>Configure if sessions created by this stage should be bound to their GeoIP-based location</source>
</trans-unit>
<trans-unit id="sa06cd519ff151b6d">
<source>RAC</source>
</trans-unit>
<trans-unit id="s634e2fd82c397576">
<source>Successfully updated settings.</source>
</trans-unit>
<trans-unit id="sb8e4edaea6f1d935">
<source>Avatars</source>
</trans-unit>
<trans-unit id="s945856050217c828">
<source>Configure how authentik should show avatars for users. The following values can be set:</source>
</trans-unit>
<trans-unit id="sf4ef4c8ce713f775">
<source>Disables per-user avatars and just shows a 1x1 pixel transparent picture</source>
</trans-unit>
<trans-unit id="s5446842a7e4a963b">
<source>Uses gravatar with the user's email address</source>
</trans-unit>
<trans-unit id="s35363b9e1cc2abd3">
<source>Generated avatars based on the user's name</source>
</trans-unit>
<trans-unit id="s48110ca292cad513">
<source>Any URL: If you want to use images hosted on another server, you can set any URL. Additionally, these placeholders can be used:</source>
</trans-unit>
<trans-unit id="sbe1dfda044bdc93b">
<source>The user's username</source>
</trans-unit>
<trans-unit id="s653f257c9c2d4dc5">
<source>The email address, md5 hashed</source>
</trans-unit>
<trans-unit id="s9c9183cd80916b4f">
<source>The user's UPN, if set (otherwise an empty string)</source>
</trans-unit>
<trans-unit id="h4963ed14d7e239a9">
<source>An attribute path like
<x id="0" equiv-text="&lt;code&gt;"/>attributes.something.avatar<x id="1" equiv-text="&lt;/code&gt;"/>, which can be used in
combination with the file field to allow users to upload custom
avatars for themselves.</source>
</trans-unit>
<trans-unit id="s4c80c34a67a6f1c9">
<source>Multiple values can be set, comma-separated, and authentik will fallback to the next mode when no avatar could be found.</source>
</trans-unit>
<trans-unit id="h2fafcc3ebafea2f8">
<source>For example, setting this to <x id="0" equiv-text="&lt;code&gt;"/>gravatar,initials<x id="1" equiv-text="&lt;/code&gt;"/> will
attempt to get an avatar from Gravatar, and if the user has not
configured on there, it will fallback to a generated avatar.</source>
</trans-unit>
<trans-unit id="sea247dfb18f696d2">
<source>Default user change name</source>
</trans-unit>
<trans-unit id="s078ffec0257621c0">
<source>Enable the ability for users to change their name.</source>
</trans-unit>
<trans-unit id="s26ad11b21253152b">
<source>Default user change email</source>
</trans-unit>
<trans-unit id="s5fc6c14d106f40d3">
<source>Enable the ability for users to change their email.</source>
</trans-unit>
<trans-unit id="s23564aa839755965">
<source>Default user change username</source>
</trans-unit>
<trans-unit id="s6d816a95ca43a99d">
<source>Enable the ability for users to change their username.</source>
</trans-unit>
<trans-unit id="s57b52b60ed5e2bc7">
<source>Footer links</source>
</trans-unit>
<trans-unit id="s7349802b2f7f99c2">
<source>This option configures the footer links on the flow executor pages. It must be a valid JSON list and can be used as follows:</source>
</trans-unit>
<trans-unit id="s166b59f3cc5d8ec3">
<source>GDPR compliance</source>
</trans-unit>
<trans-unit id="sb8b23770f899e5bb">
<source>When enabled, all the events caused by a user will be deleted upon the user's deletion.</source>
</trans-unit>
<trans-unit id="s29501761df0fe837">
<source>Impersonation</source>
</trans-unit>
<trans-unit id="s8f503553d8432487">
<source>Globally enable/disable impersonation.</source>
</trans-unit>
<trans-unit id="see1eb81c1f734079">
<source>System settings</source>
</trans-unit>
<trans-unit id="s28b99b59541f54ca">
<source>Connection failed after <x id="0" equiv-text="${this.connectionAttempt}"/> attempts.</source>
</trans-unit>
<trans-unit id="s7c7d956418e1c8c8">
<source>Re-connecting in <x id="0" equiv-text="${Math.max(1, delay / 1000)}"/> second(s).</source>
</trans-unit>
<trans-unit id="sfc003381f593d943">
<source>Connecting...</source>
</trans-unit>
<trans-unit id="s31aa94a0b3c7edb2">
<source>Select endpoint to connect to</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

File diff suppressed because it is too large Load Diff

View File

@ -6315,6 +6315,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -6345,6 +6357,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -8040,6 +8040,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -8070,6 +8082,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -6101,6 +6101,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -6131,6 +6143,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

5148
web/xliff/zh-CN.xlf Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8145,6 +8145,22 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
<target>设置会话在被断开连接并需要重新授权之前持续的时间。</target> <target>设置会话在被断开连接并需要重新授权之前持续的时间。</target>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
<target>提供程序需要企业版。</target>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
<target>了解更多</target>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
<target>最大并发连接数</target>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
<target>允许到此端点的最大并发连接数。可以设置为 -1 以禁用限制。</target>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -8175,6 +8191,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -6149,6 +6149,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -6179,6 +6191,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -8203,6 +8203,22 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
<target>设置会话在被断开连接并需要重新授权之前持续的时间。</target> <target>设置会话在被断开连接并需要重新授权之前持续的时间。</target>
</trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
<target>提供程序需要企业版。</target>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
<target>了解更多</target>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
<target>最大并发连接数</target>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
<target>允许到此端点的最大并发连接数。可以设置为 -1 以禁用限制。</target>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8025,6 +8025,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s6dd297c217729828"> <trans-unit id="s6dd297c217729828">
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source> <source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
</trans-unit> </trans-unit>
<trans-unit id="scc7f34824150bfb8">
<source>Provider require enterprise.</source>
</trans-unit>
<trans-unit id="s31f1afc1bfe1cb3a">
<source>Learn more</source>
</trans-unit>
<trans-unit id="sc39f6abf0daedb0f">
<source>Maximum concurrent connections</source>
</trans-unit>
<trans-unit id="s62418cbcd2a25498">
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
</trans-unit>
<trans-unit id="s744401846fea6e76"> <trans-unit id="s744401846fea6e76">
<source>Brand</source> <source>Brand</source>
</trans-unit> </trans-unit>
@ -8055,6 +8067,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa9d13ce9e83aac17"> <trans-unit id="sa9d13ce9e83aac17">
<source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source> <source>To let a user directly reset a their password, configure a recovery flow on the currently active brand.</source>
</trans-unit> </trans-unit>
<trans-unit id="s94d61907ee22a8c1">
<source>Korean</source>
</trans-unit>
<trans-unit id="s95d56e58f816d211">
<source>Dutch</source>
</trans-unit>
<trans-unit id="s6709b81e1ed4e39f"> <trans-unit id="s6709b81e1ed4e39f">
<source>The current brand must have a recovery flow configured to use a recovery link</source> <source>The current brand must have a recovery flow configured to use a recovery link</source>
</trans-unit> </trans-unit>

View File

@ -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. 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. Starting with authentik 2022.8, flows will be exported as YAML, but JSON-based flows can still be imported.

View File

@ -35,12 +35,20 @@ To access the user's email address, a scope of `user:email` is required. To acce
### `authorization_code`: ### `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_token`:
Refresh tokens can be used as long-lived tokens to access user data, and further renew the refresh token down the road. 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`: ### `client_credentials`:
See [Machine-to-machine authentication](./client_credentials) See [Machine-to-machine authentication](./client_credentials)

Some files were not shown because too many files have changed in this diff Show More