Compare commits

..

6 Commits

Author SHA1 Message Date
d410083cfc wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 18:31:45 +02:00
6045f96a05 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:55:20 +02:00
c50df0f843 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:54:22 +02:00
c8ebd9f74b wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:29:14 +02:00
b3f441f2cd add basic migrations
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:13:38 +02:00
647f054be3 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:11:18 +02:00
129 changed files with 6515 additions and 6979 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 2025.6.2 current_version = 2025.6.1
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))? parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -202,7 +202,7 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: web/dist path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
- name: prepare web ui - name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true' if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web working-directory: web

View File

@ -77,7 +77,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
# Stage 4: Download uv # Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.7.13 AS uv FROM ghcr.io/astral-sh/uv:0.7.13 AS uv
# Stage 5: Base python image # Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base FROM ghcr.io/goauthentik/fips-python:3.13.4-slim-bookworm-fips AS python-base
ENV VENV_PATH="/ak-root/.venv" \ ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \ PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \

View File

@ -94,7 +94,7 @@ gen-build: ## Extract the schema from the database
AUTHENTIK_DEBUG=true \ AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \ AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak make_blueprint_schema --file blueprints/schema.json uv run ak make_blueprint_schema > blueprints/schema.json
AUTHENTIK_DEBUG=true \ AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \ AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \

View File

@ -2,7 +2,7 @@
from os import environ from os import environ
__version__ = "2025.6.2" __version__ = "2025.6.1"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -72,33 +72,20 @@ class Command(BaseCommand):
"additionalProperties": True, "additionalProperties": True,
}, },
"entries": { "entries": {
"anyOf": [ "type": "array",
{ "items": {
"type": "array", "oneOf": [],
"items": {"$ref": "#/$defs/blueprint_entry"}, },
},
{
"type": "object",
"additionalProperties": {
"type": "array",
"items": {"$ref": "#/$defs/blueprint_entry"},
},
},
],
}, },
}, },
"$defs": {"blueprint_entry": {"oneOf": []}}, "$defs": {},
} }
def add_arguments(self, parser):
parser.add_argument("--file", type=str)
@no_translations @no_translations
def handle(self, *args, file: str, **options): def handle(self, *args, **options):
"""Generate JSON Schema for blueprints""" """Generate JSON Schema for blueprints"""
self.build() self.build()
with open(file, "w") as _schema: self.stdout.write(dumps(self.schema, indent=4, default=Command.json_default))
_schema.write(dumps(self.schema, indent=4, default=Command.json_default))
@staticmethod @staticmethod
def json_default(value: Any) -> Any: def json_default(value: Any) -> Any:
@ -125,7 +112,7 @@ class Command(BaseCommand):
} }
) )
model_path = f"{model._meta.app_label}.{model._meta.model_name}" model_path = f"{model._meta.app_label}.{model._meta.model_name}"
self.schema["$defs"]["blueprint_entry"]["oneOf"].append( self.schema["properties"]["entries"]["items"]["oneOf"].append(
self.template_entry(model_path, model, serializer) self.template_entry(model_path, model, serializer)
) )

View File

@ -1,11 +1,10 @@
version: 1 version: 1
entries: entries:
foo: - identifiers:
- identifiers: name: "%(id)s"
name: "%(id)s" slug: "%(id)s"
slug: "%(id)s" model: authentik_flows.flow
model: authentik_flows.flow state: present
state: present attrs:
attrs: designation: stage_configuration
designation: stage_configuration title: foo
title: foo

View File

@ -191,18 +191,11 @@ class Blueprint:
"""Dataclass used for a full export""" """Dataclass used for a full export"""
version: int = field(default=1) version: int = field(default=1)
entries: list[BlueprintEntry] | dict[str, list[BlueprintEntry]] = field(default_factory=list) entries: list[BlueprintEntry] = field(default_factory=list)
context: dict = field(default_factory=dict) context: dict = field(default_factory=dict)
metadata: BlueprintMetadata | None = field(default=None) metadata: BlueprintMetadata | None = field(default=None)
def iter_entries(self) -> Iterable[BlueprintEntry]:
if isinstance(self.entries, dict):
for _section, entries in self.entries.items():
yield from entries
else:
yield from self.entries
class YAMLTag: class YAMLTag:
"""Base class for all YAML Tags""" """Base class for all YAML Tags"""
@ -233,7 +226,7 @@ class KeyOf(YAMLTag):
self.id_from = node.value self.id_from = node.value
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
for _entry in blueprint.iter_entries(): for _entry in blueprint.entries:
if _entry.id == self.id_from and _entry._state.instance: if _entry.id == self.id_from and _entry._state.instance:
# Special handling for PolicyBindingModels, as they'll have a different PK # Special handling for PolicyBindingModels, as they'll have a different PK
# which is used when creating policy bindings # which is used when creating policy bindings

View File

@ -384,7 +384,7 @@ class Importer:
def _apply_models(self, raise_errors=False) -> bool: def _apply_models(self, raise_errors=False) -> bool:
"""Apply (create/update) models yaml""" """Apply (create/update) models yaml"""
self.__pk_map = {} self.__pk_map = {}
for entry in self._import.iter_entries(): for entry in self._import.entries:
model_app_label, model_name = entry.get_model(self._import).split(".") model_app_label, model_name = entry.get_model(self._import).split(".")
try: try:
model: type[SerializerModel] = registry.get_model(model_app_label, model_name) model: type[SerializerModel] = registry.get_model(model_app_label, model_name)

View File

@ -0,0 +1,32 @@
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import File
LOGGER = get_logger()
class FileSerializer(ModelSerializer):
class Meta:
model = File
fields = (
"pk",
"name",
"content",
"location",
"private",
"url",
)
class FileViewSet(UsedByMixin, ModelViewSet):
queryset = File.objects.all()
serializer_class = FileSerializer
filterset_fields = ("private",)
ordering = ("name",)
search_fields = (
"name",
"location",
)

View File

@ -0,0 +1,44 @@
# Generated by Django 5.1.11 on 2025-06-13 15:12
import uuid
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0048_delete_oldauthenticatedsession_content_type"),
]
operations = [
migrations.CreateModel(
name="File",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField()),
("content", models.BinaryField()),
("public", models.BooleanField(default=False)),
],
options={
"verbose_name": "Files",
},
),
migrations.RenameField(
model_name="application",
old_name="meta_icon",
new_name="meta_old_icon",
),
migrations.AddField(
model_name="application",
name="meta_icon",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.SET_NULL, to="authentik_core.file"
),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 5.1.11 on 2025-06-13 15:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0049_file_rename_meta_icon_application_meta_old_icon"),
]
operations = [
migrations.AddField(
model_name="file",
name="location",
field=models.TextField(null=True),
),
migrations.AlterField(
model_name="file",
name="content",
field=models.BinaryField(null=True),
),
migrations.AddConstraint(
model_name="file",
constraint=models.CheckConstraint(
condition=models.Q(
("content__isnull", False), ("location__isnull", False), _connector="OR"
),
name="one_of_content_location_is_defined",
),
),
]

View File

@ -29,6 +29,7 @@ from authentik.blueprints.models import ManagedModel
from authentik.core.expression.exceptions import PropertyMappingExpressionException from authentik.core.expression.exceptions import PropertyMappingExpressionException
from authentik.core.types import UILoginButton, UserSettingSerializer from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.lib.avatars import get_avatar from authentik.lib.avatars import get_avatar
from authentik.lib.config import CONFIG
from authentik.lib.expression.exceptions import ControlFlowException from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.lib.merge import MERGE_LIST_UNIQUE from authentik.lib.merge import MERGE_LIST_UNIQUE
@ -533,12 +534,13 @@ class Application(SerializerModel, PolicyBindingModel):
) )
# For template applications, this can be set to /static/authentik/applications/* # For template applications, this can be set to /static/authentik/applications/*
meta_icon = models.FileField( meta_old_icon = models.FileField(
upload_to="application-icons/", upload_to="application-icons/",
default=None, default=None,
null=True, null=True,
max_length=500, max_length=500,
) )
meta_icon = models.ForeignKey("File", null=True, on_delete=models.SET_NULL)
meta_description = models.TextField(default="", blank=True) meta_description = models.TextField(default="", blank=True)
meta_publisher = models.TextField(default="", blank=True) meta_publisher = models.TextField(default="", blank=True)
@ -1100,3 +1102,44 @@ class AuthenticatedSession(SerializerModel):
session=Session.objects.filter(session_key=request.session.session_key).first(), session=Session.objects.filter(session_key=request.session.session_key).first(),
user=user, user=user,
) )
class File(SerializerModel):
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField()
content = models.BinaryField(null=True)
location = models.TextField(null=True)
public = models.BooleanField(default=False)
delete_on_delete = models.BooleanField(default=False)
expiry = models.DateTimeField()
class Meta:
verbose_name = _("File")
verbose_name = _("Files")
constraints = (
models.CheckConstraint(
condition=Q(content__isnull=False) | Q(location__isnull=False),
name="one_of_content_location_is_defined",
),
)
def __str__(self) -> str:
return self.name
@property
def serializer(self) -> type[Serializer]:
from authentik.core.api.files import FileSerializer
return FileSerializer
@property
def url(self) -> str:
if self.content:
return (
CONFIG.get("web.path", "/")[:-1]
+ f"/files/{'public' if self.public else 'private'}/{self.pk}"
)
elif self.location.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.location
return self.location

View File

@ -8,6 +8,7 @@ from authentik.core.api.application_entitlements import ApplicationEntitlementVi
from authentik.core.api.applications import ApplicationViewSet from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
from authentik.core.api.files import FileViewSet
from authentik.core.api.groups import GroupViewSet from authentik.core.api.groups import GroupViewSet
from authentik.core.api.property_mappings import PropertyMappingViewSet from authentik.core.api.property_mappings import PropertyMappingViewSet
from authentik.core.api.providers import ProviderViewSet from authentik.core.api.providers import ProviderViewSet
@ -78,6 +79,7 @@ api_urlpatterns = [
TransactionalApplicationView.as_view(), TransactionalApplicationView.as_view(),
name="core-transactional-application", name="core-transactional-application",
), ),
("core/files", FileViewSet),
("core/groups", GroupViewSet), ("core/groups", GroupViewSet),
("core/users", UserViewSet), ("core/users", UserViewSet),
("core/tokens", TokenViewSet), ("core/tokens", TokenViewSet),

View File

@ -387,7 +387,8 @@ class TestAuthorize(OAuthTestCase):
self.assertEqual( self.assertEqual(
response.url, response.url,
( (
f"http://localhost#id_token={provider.encode(token.id_token.to_dict())}" f"http://localhost#access_token={token.token}"
f"&id_token={provider.encode(token.id_token.to_dict())}"
f"&token_type={TOKEN_TYPE}" f"&token_type={TOKEN_TYPE}"
f"&expires_in={int(expires)}&state={state}" f"&expires_in={int(expires)}&state={state}"
), ),
@ -562,6 +563,7 @@ class TestAuthorize(OAuthTestCase):
"url": "http://localhost", "url": "http://localhost",
"title": f"Redirecting to {app.name}...", "title": f"Redirecting to {app.name}...",
"attrs": { "attrs": {
"access_token": token.token,
"id_token": provider.encode(token.id_token.to_dict()), "id_token": provider.encode(token.id_token.to_dict()),
"token_type": TOKEN_TYPE, "token_type": TOKEN_TYPE,
"expires_in": "3600", "expires_in": "3600",

View File

@ -150,12 +150,12 @@ class OAuthAuthorizationParams:
self.check_redirect_uri() self.check_redirect_uri()
self.check_grant() self.check_grant()
self.check_scope(github_compat) self.check_scope(github_compat)
self.check_nonce()
self.check_code_challenge()
if self.request: if self.request:
raise AuthorizeError( raise AuthorizeError(
self.redirect_uri, "request_not_supported", self.grant_type, self.state self.redirect_uri, "request_not_supported", self.grant_type, self.state
) )
self.check_nonce()
self.check_code_challenge()
def check_grant(self): def check_grant(self):
"""Check grant""" """Check grant"""
@ -630,6 +630,7 @@ class OAuthFulfillmentStage(StageView):
if self.params.response_type in [ if self.params.response_type in [
ResponseTypes.ID_TOKEN_TOKEN, ResponseTypes.ID_TOKEN_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN, ResponseTypes.CODE_ID_TOKEN_TOKEN,
ResponseTypes.ID_TOKEN,
ResponseTypes.CODE_TOKEN, ResponseTypes.CODE_TOKEN,
]: ]:
query_fragment["access_token"] = token.token query_fragment["access_token"] = token.token

View File

@ -20,9 +20,6 @@ from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine from authentik.policies.engine import PolicyEngine
from authentik.policies.views import PolicyAccessView from authentik.policies.views import PolicyAccessView
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
PLAN_CONNECTION_SETTINGS = "connection_settings"
class RACStartView(PolicyAccessView): class RACStartView(PolicyAccessView):
@ -112,15 +109,10 @@ class RACFinalStage(RedirectStage):
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:
settings = self.executor.plan.context.get(PLAN_CONNECTION_SETTINGS)
if not settings:
settings = self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}).get(
PLAN_CONNECTION_SETTINGS
)
token = ConnectionToken.objects.create( token = ConnectionToken.objects.create(
provider=self.provider, provider=self.provider,
endpoint=self.endpoint, endpoint=self.endpoint,
settings=settings or {}, settings=self.executor.plan.context.get("connection_settings", {}),
session=self.request.session["authenticatedsession"], session=self.request.session["authenticatedsession"],
expires=now() + timedelta_from_string(self.provider.connection_expiry), expires=now() + timedelta_from_string(self.provider.connection_expiry),
expiring=True, expiring=True,

View File

@ -190,7 +190,6 @@ class SAMLProviderSerializer(ProviderSerializer):
"sign_response", "sign_response",
"sp_binding", "sp_binding",
"default_relay_state", "default_relay_state",
"default_name_id_policy",
"url_download_metadata", "url_download_metadata",
"url_sso_post", "url_sso_post",
"url_sso_redirect", "url_sso_redirect",

View File

@ -1,31 +0,0 @@
# Generated by Django 5.1.11 on 2025-06-18 09:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0018_alter_samlprovider_acs_url"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="default_name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"),
],
default="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
),
),
]

View File

@ -12,7 +12,6 @@ from authentik.core.models import PropertyMapping, Provider
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.lib.models import DomainlessURLValidator from authentik.lib.models import DomainlessURLValidator
from authentik.lib.utils.time import timedelta_string_validator from authentik.lib.utils.time import timedelta_string_validator
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import ( from authentik.sources.saml.processors.constants import (
DSA_SHA1, DSA_SHA1,
ECDSA_SHA1, ECDSA_SHA1,
@ -180,9 +179,6 @@ class SAMLProvider(Provider):
default_relay_state = models.TextField( default_relay_state = models.TextField(
default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins") default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins")
) )
default_name_id_policy = models.TextField(
choices=SAMLNameIDPolicy.choices, default=SAMLNameIDPolicy.UNSPECIFIED
)
sign_assertion = models.BooleanField(default=True) sign_assertion = models.BooleanField(default=True)
sign_response = models.BooleanField(default=False) sign_response = models.BooleanField(default=False)

View File

@ -205,13 +205,6 @@ class AssertionProcessor:
def get_name_id(self) -> Element: def get_name_id(self) -> Element:
"""Get NameID Element""" """Get NameID Element"""
name_id = Element(f"{{{NS_SAML_ASSERTION}}}NameID") name_id = Element(f"{{{NS_SAML_ASSERTION}}}NameID")
# For requests that don't specify a NameIDPolicy, check if we
# can fall back to the provider default
if (
self.auth_n_request.name_id_policy == SAML_NAME_ID_FORMAT_UNSPECIFIED
and self.provider.default_name_id_policy != SAML_NAME_ID_FORMAT_UNSPECIFIED
):
self.auth_n_request.name_id_policy = self.provider.default_name_id_policy
name_id.attrib["Format"] = self.auth_n_request.name_id_policy name_id.attrib["Format"] = self.auth_n_request.name_id_policy
# persistent is used as a fallback, so always generate it # persistent is used as a fallback, so always generate it
persistent = self.http_request.user.uid persistent = self.http_request.user.uid

View File

@ -13,7 +13,6 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.exceptions import CannotHandleAssertion from authentik.providers.saml.exceptions import CannotHandleAssertion
from authentik.providers.saml.models import SAMLProvider from authentik.providers.saml.models import SAMLProvider
from authentik.providers.saml.utils.encoding import decode_base64_and_inflate from authentik.providers.saml.utils.encoding import decode_base64_and_inflate
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import ( from authentik.sources.saml.processors.constants import (
DSA_SHA1, DSA_SHA1,
NS_MAP, NS_MAP,
@ -176,9 +175,7 @@ class AuthNRequestParser:
def idp_initiated(self) -> AuthNRequest: def idp_initiated(self) -> AuthNRequest:
"""Create IdP Initiated AuthNRequest""" """Create IdP Initiated AuthNRequest"""
request = AuthNRequest(relay_state=None) relay_state = None
if self.provider.default_relay_state != "": if self.provider.default_relay_state != "":
request.relay_state = self.provider.default_relay_state relay_state = self.provider.default_relay_state
if self.provider.default_name_id_policy != SAMLNameIDPolicy.UNSPECIFIED: return AuthNRequest(relay_state=relay_state)
request.name_id_policy = self.provider.default_name_id_policy
return request

View File

@ -13,7 +13,6 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.utils.encoding import PEM_FOOTER, PEM_HEADER from authentik.providers.saml.utils.encoding import PEM_FOOTER, PEM_HEADER
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import ( from authentik.sources.saml.processors.constants import (
NS_MAP, NS_MAP,
NS_SAML_METADATA, NS_SAML_METADATA,
@ -47,7 +46,6 @@ class ServiceProviderMetadata:
auth_n_request_signed: bool auth_n_request_signed: bool
assertion_signed: bool assertion_signed: bool
name_id_policy: SAMLNameIDPolicy
signing_keypair: CertificateKeyPair | None = None signing_keypair: CertificateKeyPair | None = None
@ -62,7 +60,6 @@ class ServiceProviderMetadata:
provider.issuer = self.entity_id provider.issuer = self.entity_id
provider.sp_binding = self.acs_binding provider.sp_binding = self.acs_binding
provider.acs_url = self.acs_location provider.acs_url = self.acs_location
provider.default_name_id_policy = self.name_id_policy
if self.signing_keypair and self.auth_n_request_signed: if self.signing_keypair and self.auth_n_request_signed:
self.signing_keypair.name = f"Provider {name} - SAML Signing Certificate" self.signing_keypair.name = f"Provider {name} - SAML Signing Certificate"
self.signing_keypair.save() self.signing_keypair.save()
@ -151,11 +148,6 @@ class ServiceProviderMetadataParser:
if signing_keypair: if signing_keypair:
self.check_signature(root, signing_keypair) self.check_signature(root, signing_keypair)
name_id_format = descriptor.findall(f"{{{NS_SAML_METADATA}}}NameIDFormat")
name_id_policy = SAMLNameIDPolicy.UNSPECIFIED
if len(name_id_format) > 0:
name_id_policy = SAMLNameIDPolicy(name_id_format[0].text)
return ServiceProviderMetadata( return ServiceProviderMetadata(
entity_id=entity_id, entity_id=entity_id,
acs_binding=acs_binding, acs_binding=acs_binding,
@ -163,5 +155,4 @@ class ServiceProviderMetadataParser:
auth_n_request_signed=auth_n_request_signed, auth_n_request_signed=auth_n_request_signed,
assertion_signed=assertion_signed, assertion_signed=assertion_signed,
signing_keypair=signing_keypair, signing_keypair=signing_keypair,
name_id_policy=name_id_policy,
) )

View File

@ -4,7 +4,7 @@
cacheDuration="PT604800S" cacheDuration="PT604800S"
entityID="http://localhost:8080/saml/metadata"> entityID="http://localhost:8080/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat> <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/saml/acs" Location="http://localhost:8080/saml/acs"
index="1" /> index="1" />

View File

@ -14,7 +14,6 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.metadata import MetadataProcessor from authentik.providers.saml.processors.metadata import MetadataProcessor
from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA from authentik.sources.saml.processors.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA
@ -87,7 +86,6 @@ class TestServiceProviderMetadataParser(TestCase):
self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs") self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs")
self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata") self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata")
self.assertEqual(provider.sp_binding, SAMLBindings.POST) self.assertEqual(provider.sp_binding, SAMLBindings.POST)
self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL)
self.assertEqual( self.assertEqual(
len(provider.property_mappings.all()), len(provider.property_mappings.all()),
len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)), len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)),

View File

@ -166,7 +166,6 @@ SPECTACULAR_SETTINGS = {
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification", "UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
"UserTypeEnum": "authentik.core.models.UserTypes", "UserTypeEnum": "authentik.core.models.UserTypes",
"OutgoingSyncDeleteAction": "authentik.lib.sync.outgoing.models.OutgoingSyncDeleteAction", "OutgoingSyncDeleteAction": "authentik.lib.sync.outgoing.models.OutgoingSyncDeleteAction",
"SAMLNameIDPolicyEnum": "authentik.sources.saml.models.SAMLNameIDPolicy",
}, },
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False, "ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
"ENUM_GENERATE_CHOICE_DESCRIPTION": False, "ENUM_GENERATE_CHOICE_DESCRIPTION": False,

View File

@ -1,32 +0,0 @@
# Generated by Django 5.1.11 on 2025-06-18 09:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_saml", "0019_migrate_usersamlsourceconnection_identifier"),
]
operations = [
migrations.AlterField(
model_name="samlsource",
name="name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"),
],
default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
),
),
]

View File

@ -39,7 +39,6 @@ from authentik.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT, SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_UNSPECIFIED,
SAML_NAME_ID_FORMAT_WINDOWS, SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509, SAML_NAME_ID_FORMAT_X509,
SHA1, SHA1,
@ -74,7 +73,6 @@ class SAMLNameIDPolicy(models.TextChoices):
X509 = SAML_NAME_ID_FORMAT_X509 X509 = SAML_NAME_ID_FORMAT_X509
WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS
TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT
UNSPECIFIED = SAML_NAME_ID_FORMAT_UNSPECIFIED
class SAMLSource(Source): class SAMLSource(Source):

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ services:
volumes: volumes:
- redis:/data - redis:/data
server: server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1}
restart: unless-stopped restart: unless-stopped
command: server command: server
environment: environment:
@ -55,7 +55,7 @@ services:
redis: redis:
condition: service_healthy condition: service_healthy
worker: worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1}
restart: unless-stopped restart: unless-stopped
command: worker command: worker
environment: environment:

18
go.mod
View File

@ -18,7 +18,7 @@ require (
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/grafana/pyroscope-go v1.2.2 github.com/grafana/pyroscope-go v1.2.2
github.com/jellydator/ttlcache/v3 v3.4.0 github.com/jellydator/ttlcache/v3 v3.3.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
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.8.1 github.com/pires/go-proxyproto v0.8.1
@ -29,7 +29,7 @@ require (
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2 github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025062.1 goauthentik.io/api/v3 v3.2025061.2
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.15.0 golang.org/x/sync v0.15.0
@ -62,6 +62,12 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.5 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
@ -77,9 +83,11 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.39.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.26.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/gorm v1.30.0 // indirect
) )

30
go.sum
View File

@ -191,6 +191,14 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@ -203,8 +211,12 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -298,8 +310,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025062.1 h1:spvILDpDDWJNO3pM6QGqmryx6NvSchr1E8H60J/XUCA= goauthentik.io/api/v3 v3.2025061.2 h1:bKmrl82Gz6J8lz3f+QIH9g+MEkl3MvkMXF34GktesA0=
goauthentik.io/api/v3 v3.2025062.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= goauthentik.io/api/v3 v3.2025061.2/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=
@ -308,6 +320,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
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=
@ -415,6 +429,8 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -422,6 +438,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -555,6 +573,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -5,6 +5,7 @@ type Config struct {
Storage StorageConfig `yaml:"storage"` Storage StorageConfig `yaml:"storage"`
LogLevel string `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL, overwrite"` LogLevel string `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL, overwrite"`
ErrorReporting ErrorReportingConfig `yaml:"error_reporting" env:", prefix=AUTHENTIK_ERROR_REPORTING__"` ErrorReporting ErrorReportingConfig `yaml:"error_reporting" env:", prefix=AUTHENTIK_ERROR_REPORTING__"`
Postgresql PostgresqlConfig `yaml:"postgresql" env:", prefix=AUTHENTIK_POSTGRESQL__"`
Redis RedisConfig `yaml:"redis" env:", prefix=AUTHENTIK_REDIS__"` Redis RedisConfig `yaml:"redis" env:", prefix=AUTHENTIK_REDIS__"`
Outposts OutpostConfig `yaml:"outposts" env:", prefix=AUTHENTIK_OUTPOSTS__"` Outposts OutpostConfig `yaml:"outposts" env:", prefix=AUTHENTIK_OUTPOSTS__"`
@ -25,6 +26,16 @@ type Config struct {
AuthentikInsecure bool `env:"AUTHENTIK_INSECURE"` AuthentikInsecure bool `env:"AUTHENTIK_INSECURE"`
} }
// TODO: SSL
type PostgresqlConfig struct {
Host string `yaml:"host" env:"HOST, overwrite"`
Port string `yaml:"port" env:"PORT, overwrite"`
User string `yaml:"user" env:"USER, overwrite"`
Password string `yaml:"password" env:"PASSWORD, overwrite"`
Name string `yaml:"name" env:"NAME, overwrite"`
DefaultSchema string `yaml:"default_schema" env:"DEFAULT_SCHEMA, overwrite"`
}
type RedisConfig struct { type RedisConfig struct {
Host string `yaml:"host" env:"HOST, overwrite"` Host string `yaml:"host" env:"HOST, overwrite"`
Port int `yaml:"port" env:"PORT, overwrite"` Port int `yaml:"port" env:"PORT, overwrite"`

View File

@ -33,4 +33,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion()) return fmt.Sprintf("authentik@%s", FullVersion())
} }
const VERSION = "2025.6.2" const VERSION = "2025.6.1"

62
internal/web/files.go Normal file
View File

@ -0,0 +1,62 @@
package web
import (
"net/http"
"github.com/go-http-utils/etag"
"github.com/gorilla/mux"
"goauthentik.io/internal/config"
"goauthentik.io/internal/constants"
)
type File struct {
ID string `gorm:"primaryKey"`
Name string
Content []byte
Location string
Public bool
}
func (ws *WebServer) configureFiles() {
// Setup routers
filesRouter := ws.loggingRouter.NewRoute().Subrouter()
filesRouter.Use(ws.filesHeaderMiddleware)
filesRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/files/public/{pk}").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pk := vars["pk"]
var file File
ws.postgresClient.First(&file, "id = ? AND public = true AND content <> NULL", pk)
// TODO: get from DB
rw.Write(file.Content)
})
filesRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/files/private/{pk}").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// TODO: check session
pk := vars["pk"]
var file File
ws.postgresClient.First(&file, "id = ? AND content <> NULL", pk)
rw.Write([]byte(file.Content))
})
}
// TODO: anything else?
func (ws *WebServer) filesHeaderMiddleware(h http.Handler) http.Handler {
etagHandler := etag.Handler(h, false)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, no-transform")
w.Header().Set("X-authentik-version", constants.VERSION)
w.Header().Set("Vary", "X-authentik-version, Etag")
etagHandler.ServeHTTP(w, r)
})
}

View File

@ -17,6 +17,8 @@ import (
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
@ -49,6 +51,7 @@ type WebServer struct {
mainRouter *mux.Router mainRouter *mux.Router
loggingRouter *mux.Router loggingRouter *mux.Router
log *log.Entry log *log.Entry
postgresClient *gorm.DB
upstreamClient *http.Client upstreamClient *http.Client
upstreamURL *url.URL upstreamURL *url.URL
@ -64,6 +67,21 @@ func NewWebServer() *WebServer {
loggingHandler := mainHandler.NewRoute().Subrouter() loggingHandler := mainHandler.NewRoute().Subrouter()
loggingHandler.Use(web.NewLoggingHandler(l, nil)) loggingHandler.Use(web.NewLoggingHandler(l, nil))
// TODO: ssl
postgresDsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
config.Get().Postgresql.Host,
config.Get().Postgresql.Port,
config.Get().Postgresql.User,
config.Get().Postgresql.Password,
config.Get().Postgresql.Name,
)
db, err := gorm.Open(postgres.Open(postgresDsn), &gorm.Config{})
if err != nil {
panic(err)
}
tmp := os.TempDir() tmp := os.TempDir()
socketPath := path.Join(tmp, UnixSocketName) socketPath := path.Join(tmp, UnixSocketName)
@ -88,6 +106,7 @@ func NewWebServer() *WebServer {
mainRouter: mainHandler, mainRouter: mainHandler,
loggingRouter: loggingHandler, loggingRouter: loggingHandler,
log: l, log: l,
postgresClient: db,
gunicornReady: false, gunicornReady: false,
upstreamClient: upstreamClient, upstreamClient: upstreamClient,
upstreamURL: u, upstreamURL: u,

View File

@ -26,7 +26,7 @@ Parameters:
Description: authentik Docker image Description: authentik Docker image
AuthentikVersion: AuthentikVersion:
Type: String Type: String
Default: 2025.6.2 Default: 2025.6.1
Description: authentik Docker image tag Description: authentik Docker image tag
AuthentikServerCPU: AuthentikServerCPU:
Type: Number Type: Number

View File

@ -6,18 +6,18 @@
# Translators: # Translators:
# jcamat, 2022 # jcamat, 2022
# Angel, 2024 # Angel, 2024
# Iamanaws, 2024
# Marcelo Elizeche Landó, 2025 # Marcelo Elizeche Landó, 2025
# Jens L. <jens@goauthentik.io>, 2025 # Jens L. <jens@goauthentik.io>, 2025
# Iamanaws, 2025
# #
#, fuzzy #, fuzzy
msgid "" 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: 2025-06-04 00:12+0000\n" "POT-Creation-Date: 2025-05-28 11:25+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Iamanaws, 2025\n" "Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n" "Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -111,7 +111,7 @@ msgstr "Certificado Web usado por el servidor web Core de authentik"
#: authentik/brands/models.py #: authentik/brands/models.py
msgid "Certificates used for client authentication." msgid "Certificates used for client authentication."
msgstr "Certificados utilizados para la autenticación del cliente." msgstr ""
#: authentik/brands/models.py #: authentik/brands/models.py
msgid "Brand" msgid "Brand"
@ -131,7 +131,7 @@ msgstr "Descripción adicional no disponible."
#: authentik/core/api/groups.py #: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself." msgid "Cannot set group as parent of itself."
msgstr "No se puede establecer un grupo como su propio padre." msgstr "No se puede establecer el grupo como padre de sí mismo."
#: authentik/core/api/providers.py #: authentik/core/api/providers.py
msgid "" msgid ""
@ -183,11 +183,11 @@ msgstr "Remueve usuario del grupo"
#: authentik/core/models.py #: authentik/core/models.py
msgid "Enable superuser status" msgid "Enable superuser status"
msgstr "Habilitar el estado de superusuario" msgstr "Habiliar estado de \"superusuario\""
#: authentik/core/models.py #: authentik/core/models.py
msgid "Disable superuser status" msgid "Disable superuser status"
msgstr "Deshabilitar el estado de superusuario" msgstr "Deshabiliar estado de \"superusuario\""
#: authentik/core/models.py #: authentik/core/models.py
msgid "User's display name." msgid "User's display name."
@ -241,7 +241,7 @@ msgstr "Flujo utilizado al autorizar a este proveedor."
#: authentik/core/models.py #: authentik/core/models.py
msgid "Flow used ending the session from a provider." msgid "Flow used ending the session from a provider."
msgstr "Flujo utilizado para finalizar la sesión desde un proveedor." msgstr "Flujo usado para terminar la sesión de un proveedor."
#: authentik/core/models.py #: authentik/core/models.py
msgid "" msgid ""
@ -273,11 +273,11 @@ msgstr "Aplicaciones"
#: authentik/core/models.py #: authentik/core/models.py
msgid "Application Entitlement" msgid "Application Entitlement"
msgstr "Derecho de Aplicación" msgstr ""
#: authentik/core/models.py #: authentik/core/models.py
msgid "Application Entitlements" msgid "Application Entitlements"
msgstr "Derechos de Aplicación" msgstr ""
#: authentik/core/models.py #: authentik/core/models.py
msgid "Use the source-specific identifier" msgid "Use the source-specific identifier"
@ -288,9 +288,9 @@ msgid ""
"Link to a user with identical email address. Can have security implications " "Link to a user with identical email address. Can have security implications "
"when a source doesn't validate email addresses." "when a source doesn't validate email addresses."
msgstr "" msgstr ""
"Enlace a un usuario con la misma dirección de correo electrónico. Puede " "Apunta a un usuario con una dirección de correo electrónico idéntica. Puede "
"tener implicaciones de seguridad cuando una fuente no valida las direcciones" "tener implicaciones de seguridad cuando una fuente no valida la dirección de"
" de correo electrónico." " correo electrónico."
#: authentik/core/models.py #: authentik/core/models.py
msgid "" msgid ""
@ -305,8 +305,8 @@ msgid ""
"Link to a user with identical username. Can have security implications when " "Link to a user with identical username. Can have security implications when "
"a username is used with another source." "a username is used with another source."
msgstr "" msgstr ""
"Enlace a un usuario con el mismo nombre de usuario. Puede tener " "Enlace a un usuario con un nombre de usuario idéntico. Puede tener "
"implicaciones de seguridad cuando un nombre de usuario se utiliza con otra " "implicaciones de seguridad cuando se usa un nombre de usuario con otra "
"fuente." "fuente."
#: authentik/core/models.py #: authentik/core/models.py
@ -322,8 +322,8 @@ msgid ""
"Link to a group with identical name. Can have security implications when a " "Link to a group with identical name. Can have security implications when a "
"group name is used with another source." "group name is used with another source."
msgstr "" msgstr ""
"Enlace a un grupo con el mismo nombre. Puede tener implicaciones de " "Enlace a un grupo con un nombre idéntico. Puede tener implicaciones de "
"seguridad cuando un nombre de grupo se utiliza con otra fuente." "seguridad cuando se utiliza un nombre de grupo con otra fuente."
#: authentik/core/models.py #: authentik/core/models.py
msgid "Use the group name, but deny enrollment when the name already exists." msgid "Use the group name, but deny enrollment when the name already exists."
@ -385,7 +385,7 @@ msgstr "Asignaciones de Propiedades"
#: authentik/core/models.py #: authentik/core/models.py
msgid "session data" msgid "session data"
msgstr "datos de sesión" msgstr ""
#: authentik/core/models.py #: authentik/core/models.py
msgid "Session" msgid "Session"
@ -424,7 +424,7 @@ msgstr "¡Autenticado exitosamente con {source}!"
#: authentik/core/sources/flow_manager.py #: authentik/core/sources/flow_manager.py
#, python-brace-format #, python-brace-format
msgid "Successfully linked {source}!" msgid "Successfully linked {source}!"
msgstr "¡{source} enlazado correctamente!" msgstr "¡{source} vinculado exitosamente!"
#: authentik/core/sources/flow_manager.py #: authentik/core/sources/flow_manager.py
msgid "Source is not configured for enrollment." msgid "Source is not configured for enrollment."
@ -476,11 +476,11 @@ msgstr ""
#: authentik/crypto/models.py #: authentik/crypto/models.py
msgid "Certificate-Key Pair" msgid "Certificate-Key Pair"
msgstr "Par Certificado-Clave" msgstr "Par de claves de certificado"
#: authentik/crypto/models.py #: authentik/crypto/models.py
msgid "Certificate-Key Pairs" msgid "Certificate-Key Pairs"
msgstr "Pares Certificado-Clave" msgstr "Pares de claves de certificado"
#: authentik/enterprise/api.py #: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object." msgid "Enterprise is required to create/update this object."
@ -511,7 +511,7 @@ msgstr ""
#: authentik/enterprise/policies/unique_password/models.py #: authentik/enterprise/policies/unique_password/models.py
msgid "Number of passwords to check against." msgid "Number of passwords to check against."
msgstr "Número de contraseñas contra las que verificar." msgstr ""
#: authentik/enterprise/policies/unique_password/models.py #: authentik/enterprise/policies/unique_password/models.py
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
@ -521,20 +521,18 @@ msgstr "La contraseña no se ha establecido en contexto"
#: authentik/enterprise/policies/unique_password/models.py #: authentik/enterprise/policies/unique_password/models.py
msgid "This password has been used previously. Please choose a different one." msgid "This password has been used previously. Please choose a different one."
msgstr "" msgstr ""
"Esta contraseña se ha utilizado anteriormente. Por favor, elija una "
"diferente."
#: authentik/enterprise/policies/unique_password/models.py #: authentik/enterprise/policies/unique_password/models.py
msgid "Password Uniqueness Policy" msgid "Password Uniqueness Policy"
msgstr "Política de Unicidad de Contraseñas" msgstr ""
#: authentik/enterprise/policies/unique_password/models.py #: authentik/enterprise/policies/unique_password/models.py
msgid "Password Uniqueness Policies" msgid "Password Uniqueness Policies"
msgstr "Políticas de Unicidad de Contraseñas" msgstr ""
#: authentik/enterprise/policies/unique_password/models.py #: authentik/enterprise/policies/unique_password/models.py
msgid "User Password History" msgid "User Password History"
msgstr "Historial de Contraseñas del Usuario" msgstr ""
#: authentik/enterprise/policy.py #: authentik/enterprise/policy.py
msgid "Enterprise required to access this feature." msgid "Enterprise required to access this feature."
@ -619,39 +617,39 @@ msgstr "Clave de firma"
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "Key used to sign the SSF Events." msgid "Key used to sign the SSF Events."
msgstr "Clave utilizada para firmar los eventos SSF." msgstr ""
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "Shared Signals Framework Provider" msgid "Shared Signals Framework Provider"
msgstr "Proveedor del Marco de Señales Compartidas" msgstr ""
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "Shared Signals Framework Providers" msgid "Shared Signals Framework Providers"
msgstr "Proveedores del Marco de Señales Compartidas" msgstr ""
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "Add stream to SSF provider" msgid "Add stream to SSF provider"
msgstr "Agregar flujo de datos al proveedor SSF" msgstr ""
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "SSF Stream" msgid "SSF Stream"
msgstr "Flujo de Datos SSF" msgstr ""
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "SSF Streams" msgid "SSF Streams"
msgstr "Flujos de Datos SSF" msgstr ""
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "SSF Stream Event" msgid "SSF Stream Event"
msgstr "Evento de Flujo de Datos SSF" msgstr ""
#: authentik/enterprise/providers/ssf/models.py #: authentik/enterprise/providers/ssf/models.py
msgid "SSF Stream Events" msgid "SSF Stream Events"
msgstr "Eventos de Flujos de Datos SSF" msgstr ""
#: authentik/enterprise/providers/ssf/tasks.py #: authentik/enterprise/providers/ssf/tasks.py
msgid "Failed to send request" msgid "Failed to send request"
msgstr "Error al enviar la solicitud" msgstr "Falló envio de petición"
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py #: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
msgid "Endpoint Authenticator Google Device Trust Connector Stage" msgid "Endpoint Authenticator Google Device Trust Connector Stage"
@ -683,29 +681,26 @@ msgid ""
"option has a higher priority than the `client_certificate` option on " "option has a higher priority than the `client_certificate` option on "
"`Brand`." "`Brand`."
msgstr "" msgstr ""
"Configura las autoridades certificadoras para validar el certificado. Esta "
"opción tiene una prioridad mayor que la opción `client_certificate` en "
"`Brand`."
#: authentik/enterprise/stages/mtls/models.py #: authentik/enterprise/stages/mtls/models.py
msgid "Mutual TLS Stage" msgid "Mutual TLS Stage"
msgstr "Etapa de TLS mutuo" msgstr ""
#: authentik/enterprise/stages/mtls/models.py #: authentik/enterprise/stages/mtls/models.py
msgid "Mutual TLS Stages" msgid "Mutual TLS Stages"
msgstr "Etapas de TLS mutuo" msgstr ""
#: authentik/enterprise/stages/mtls/models.py #: authentik/enterprise/stages/mtls/models.py
msgid "Permissions to pass Certificates for outposts." msgid "Permissions to pass Certificates for outposts."
msgstr "Permisos para pasar Certificados a los puestos avanzados." msgstr ""
#: authentik/enterprise/stages/mtls/stage.py #: authentik/enterprise/stages/mtls/stage.py
msgid "Certificate required but no certificate was given." msgid "Certificate required but no certificate was given."
msgstr "Se requiere certificado, pero no se proporcionó ninguno." msgstr ""
#: authentik/enterprise/stages/mtls/stage.py #: authentik/enterprise/stages/mtls/stage.py
msgid "No user found for certificate." msgid "No user found for certificate."
msgstr "No se encontró usuario para el certificado." msgstr ""
#: authentik/enterprise/stages/source/models.py #: authentik/enterprise/stages/source/models.py
msgid "" msgid ""
@ -758,16 +753,12 @@ msgid ""
"Customize the body of the request. Mapping should return data that is JSON-" "Customize the body of the request. Mapping should return data that is JSON-"
"serializable." "serializable."
msgstr "" msgstr ""
"Personaliza el cuerpo de la solicitud. El mapeo debe devolver datos que sean"
" serializables en JSON."
#: authentik/events/models.py #: authentik/events/models.py
msgid "" msgid ""
"Configure additional headers to be sent. Mapping should return a dictionary " "Configure additional headers to be sent. Mapping should return a dictionary "
"of key-value pairs" "of key-value pairs"
msgstr "" msgstr ""
"Configura encabezados adicionales para enviar. El mapeo debe devolver un "
"diccionario de pares clave-valor"
#: authentik/events/models.py #: authentik/events/models.py
msgid "" msgid ""
@ -795,7 +786,7 @@ msgstr "Transporte de notificaciones"
#: authentik/events/models.py #: authentik/events/models.py
msgid "Notification Transports" msgid "Notification Transports"
msgstr "Medios de Notificación" msgstr "Transportes de notificación"
#: authentik/events/models.py #: authentik/events/models.py
msgid "Notice" msgid "Notice"
@ -822,9 +813,9 @@ msgid ""
"Select which transports should be used to notify the user. If none are " "Select which transports should be used to notify the user. If none are "
"selected, the notification will only be shown in the authentik UI." "selected, the notification will only be shown in the authentik UI."
msgstr "" msgstr ""
"Selecciona qué medios se deben usar para notificar al usuario. Si no se " "Seleccione qué transportes se deben usar para notificar al usuario. Si no se"
"selecciona ninguno, la notificación solo se mostrará en la interfaz de " " selecciona ninguno, la notificación solo se mostrará en la interfaz de "
"authentik." "usuario de authentik."
#: authentik/events/models.py #: authentik/events/models.py
msgid "Controls which severity level the created notifications will have." msgid "Controls which severity level the created notifications will have."
@ -996,7 +987,7 @@ msgstr "Evalúa políticas durante el proceso de planeación del Flujo."
#: authentik/flows/models.py #: authentik/flows/models.py
msgid "Evaluate policies when the Stage is presented to the user." msgid "Evaluate policies when the Stage is presented to the user."
msgstr "Evaluar las políticas cuando la Etapa se presenta al usuario." msgstr ""
#: authentik/flows/models.py #: authentik/flows/models.py
msgid "" msgid ""
@ -1043,8 +1034,6 @@ msgid ""
"When enabled, provider will not modify or create objects in the remote " "When enabled, provider will not modify or create objects in the remote "
"system." "system."
msgstr "" msgstr ""
"Cuando está habilitado, el proveedor no modificará ni creará objetos en el "
"sistema remoto."
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
msgid "Starting full provider sync" msgid "Starting full provider sync"
@ -1052,21 +1041,20 @@ msgstr "Iniciando sincronización completa de proveedor"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing users" msgid "Syncing users"
msgstr "Sincronizando usuarios" msgstr ""
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing groups" msgid "Syncing groups"
msgstr "Sincronizando grupos" msgstr ""
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-brace-format
msgid "Syncing page {page} of {object_type}" msgid "Syncing page {page} of groups"
msgstr "Sincronizando página {page} de {object_type}" msgstr "Sincronizando página {page} de grupos"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
msgid "Dropping mutating request due to dry run" msgid "Dropping mutating request due to dry run"
msgstr "" msgstr ""
"Descartando solicitud de mutación debido a ejecución en modo de simulación"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-brace-format
@ -1245,7 +1233,7 @@ msgstr ""
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
msgid "Password has expired." msgid "Password has expired."
msgstr "La contraseña ha expirado." msgstr "La contraseña ha caducado."
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
msgid "Password Expiry Policy" msgid "Password Expiry Policy"
@ -1283,7 +1271,7 @@ msgstr "La IP del cliente no está en un país permitido."
#: authentik/policies/geoip/models.py #: authentik/policies/geoip/models.py
msgid "Distance from previous authentication is larger than threshold." msgid "Distance from previous authentication is larger than threshold."
msgstr "La distancia desde la autenticación anterior es mayor que el umbral." msgstr "La distancia desde la autenticación previa es mayor que el límite."
#: authentik/policies/geoip/models.py #: authentik/policies/geoip/models.py
msgid "Distance is further than possible." msgid "Distance is further than possible."
@ -1332,7 +1320,7 @@ msgstr "Vinculación de Políticas"
#: authentik/policies/models.py #: authentik/policies/models.py
msgid "Policy Bindings" msgid "Policy Bindings"
msgstr "Vinculaciones de Políticas" msgstr "Vinculaciones de políticas"
#: authentik/policies/models.py #: authentik/policies/models.py
msgid "" msgid ""
@ -1606,11 +1594,11 @@ msgstr "ES256 (Encriptación Asimétrica)"
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "ES384 (Asymmetric Encryption)" msgid "ES384 (Asymmetric Encryption)"
msgstr "ES384 (Encriptación Asimétrica)" msgstr ""
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "ES512 (Asymmetric Encryption)" msgid "ES512 (Asymmetric Encryption)"
msgstr "ES512 (Encriptación Asimétrica)" msgstr ""
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "Scope used by the client" msgid "Scope used by the client"
@ -1825,7 +1813,7 @@ msgstr "Valida Certificados SSL de servidores de origen"
#: authentik/providers/proxy/models.py #: authentik/providers/proxy/models.py
msgid "Internal host SSL Validation" msgid "Internal host SSL Validation"
msgstr "Validación SSL del host interno" msgstr "Validación SSL de host interno"
#: authentik/providers/proxy/models.py #: authentik/providers/proxy/models.py
msgid "" msgid ""
@ -2039,7 +2027,7 @@ msgstr ""
#: authentik/providers/saml/models.py #: authentik/providers/saml/models.py
msgid "AuthnContextClassRef Property Mapping" msgid "AuthnContextClassRef Property Mapping"
msgstr "Asignación de Propiedades de AuthnContextClassRef" msgstr ""
#: authentik/providers/saml/models.py #: authentik/providers/saml/models.py
msgid "" msgid ""
@ -2047,9 +2035,6 @@ msgid ""
"empty, the AuthnContextClassRef will be set based on which authentication " "empty, the AuthnContextClassRef will be set based on which authentication "
"methods the user used to authenticate." "methods the user used to authenticate."
msgstr "" msgstr ""
"Configura cómo se creará el valor de AuthnContextClassRef. Si se deja vacío,"
" el AuthnContextClassRef se establecerá según los métodos de autenticación "
"que el usuario haya utilizado para autenticarse."
#: authentik/providers/saml/models.py #: authentik/providers/saml/models.py
msgid "" msgid ""
@ -2199,11 +2184,11 @@ msgstr "Predeterminado"
#: authentik/providers/scim/models.py #: authentik/providers/scim/models.py
msgid "AWS" msgid "AWS"
msgstr "AWS" msgstr ""
#: authentik/providers/scim/models.py #: authentik/providers/scim/models.py
msgid "Slack" msgid "Slack"
msgstr "Slack" msgstr ""
#: authentik/providers/scim/models.py #: authentik/providers/scim/models.py
msgid "Base URL to SCIM requests, usually ends in /v2" msgid "Base URL to SCIM requests, usually ends in /v2"
@ -2215,13 +2200,11 @@ msgstr "Token de Autenticación"
#: authentik/providers/scim/models.py #: authentik/providers/scim/models.py
msgid "SCIM Compatibility Mode" msgid "SCIM Compatibility Mode"
msgstr "Modo de Compatibilidad SCIM" msgstr ""
#: authentik/providers/scim/models.py #: authentik/providers/scim/models.py
msgid "Alter authentik behavior for vendor-specific SCIM implementations." msgid "Alter authentik behavior for vendor-specific SCIM implementations."
msgstr "" msgstr ""
"Modificar el comportamiento de authentik para implementaciones SCIM "
"específicas de proveedores."
#: authentik/providers/scim/models.py #: authentik/providers/scim/models.py
msgid "SCIM Provider" msgid "SCIM Provider"
@ -2249,7 +2232,7 @@ msgstr "Roles"
#: authentik/rbac/models.py #: authentik/rbac/models.py
msgid "Initial Permissions" msgid "Initial Permissions"
msgstr "Permisos Iniciales" msgstr ""
#: authentik/rbac/models.py #: authentik/rbac/models.py
msgid "System permission" msgid "System permission"
@ -2287,7 +2270,7 @@ msgstr ""
#: authentik/recovery/views.py #: authentik/recovery/views.py
msgid "Used recovery-link to authenticate." msgid "Used recovery-link to authenticate."
msgstr "Se utilizó un enlace de recuperación para autenticarse." msgstr "Se usó el enlace de recuperación para autenticarse."
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "Kerberos realm" msgid "Kerberos realm"
@ -2299,7 +2282,7 @@ msgstr "krb5.conf personalizado a usar. Usa el del sistema por defecto."
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "KAdmin server type" msgid "KAdmin server type"
msgstr "Tipo de servidor KAdmin" msgstr ""
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "Sync users from Kerberos into authentik" msgid "Sync users from Kerberos into authentik"
@ -2307,24 +2290,23 @@ msgstr "Sincronizar usuarios desde Kerberos hacia Authentik"
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "When a user changes their password, sync it back to Kerberos" msgid "When a user changes their password, sync it back to Kerberos"
msgstr "" msgstr "Cuando un usuario cambia su contraseña, sincronizarlo hacia Kerberos"
"Cuando un usuario cambie su contraseña, sincronizarla de vuelta a Kerberos."
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "Principal to authenticate to kadmin for sync." msgid "Principal to authenticate to kadmin for sync."
msgstr "Principal para autenticarse en kadmin para la sincronización." msgstr "Principal para autenticarse como kadmin para la sincronización."
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "Password to authenticate to kadmin for sync" msgid "Password to authenticate to kadmin for sync"
msgstr "Contraseña para autenticarse en kadmin para la sincronización" msgstr "Contraseña para autenticarse como kadmin para la sincronización"
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "" msgid ""
"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the " "Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the "
"form TYPE:residual" "form TYPE:residual"
msgstr "" msgstr ""
"Keytab para autenticarse en kadmin para la sincronización. Debe estar " "Keytab para autenticarse como kadmin para la sincronización. Debe estar "
"codificado en base64 o en el formato TIPO:residuo" "codificado en base64 o en el formato TIPO:residual"
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "" msgid ""
@ -2340,7 +2322,7 @@ msgid ""
"HTTP@hostname" "HTTP@hostname"
msgstr "" msgstr ""
"Forzar el uso de un nombre de servidor específico para SPNEGO. Debe estar en" "Forzar el uso de un nombre de servidor específico para SPNEGO. Debe estar en"
" el formato HTTP@nombre_de_host" " el formato HTTP@nombredelservidor"
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path" msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path"
@ -2357,8 +2339,8 @@ msgid ""
"If enabled, the authentik-stored password will be updated upon login with " "If enabled, the authentik-stored password will be updated upon login with "
"the Kerberos password backend" "the Kerberos password backend"
msgstr "" msgstr ""
"Si está habilitado, la contraseña almacenada en authentik se actualizará al " "Si está habilitado, la contraseña almacenada por authentik se actualizada "
"iniciar sesión con el backend de contraseñas de Kerberos." "al iniciar sesión con el backend de contraseñas Kerberos"
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "Kerberos Source" msgid "Kerberos Source"
@ -2406,7 +2388,7 @@ msgid ""
msgstr "" msgstr ""
"\n" "\n"
" Asegúrate de que tienes entradas válidas\n" " Asegúrate de que tienes entradas válidas\n"
" (obtenibles mediante kinit) \n" " (se obtienen a través de kinit) \n"
" y de haber configurado correctamente el navegador.\n" " y de haber configurado correctamente el navegador.\n"
" Por favor, contacta a tu administrador.\n" " Por favor, contacta a tu administrador.\n"
" " " "
@ -2471,10 +2453,6 @@ msgstr "DN de grupo de adición"
msgid "Consider Objects matching this filter to be Users." msgid "Consider Objects matching this filter to be Users."
msgstr "Considere que los objetos que coinciden con este filtro son usuarios." msgstr "Considere que los objetos que coinciden con este filtro son usuarios."
#: authentik/sources/ldap/models.py
msgid "Attribute which matches the value of `group_membership_field`."
msgstr "Atributo que coincide con el valor de `group_membership_field`."
#: authentik/sources/ldap/models.py #: authentik/sources/ldap/models.py
msgid "Field which contains members of a group." msgid "Field which contains members of a group."
msgstr "Campo que contiene los miembros de un grupo." msgstr "Campo que contiene los miembros de un grupo."
@ -2507,17 +2485,12 @@ msgid ""
"attribute. This allows nested group resolution on systems like FreeIPA and " "attribute. This allows nested group resolution on systems like FreeIPA and "
"Active Directory" "Active Directory"
msgstr "" msgstr ""
"Buscar la pertenencia a grupos basándose en un atributo del usuario en lugar"
" de un atributo del grupo. Esto permite la resolución de grupos anidados en "
"sistemas como FreeIPA y Active Directory"
#: authentik/sources/ldap/models.py #: authentik/sources/ldap/models.py
msgid "" msgid ""
"Delete authentik users and groups which were previously supplied by this " "Delete authentik users and groups which were previously supplied by this "
"source, but are now missing from it." "source, but are now missing from it."
msgstr "" msgstr ""
"Eliminar usuarios y grupos de authentik que fueron proporcionados "
"previamente por esta fuente, pero que ahora están ausentes."
#: authentik/sources/ldap/models.py #: authentik/sources/ldap/models.py
msgid "LDAP Source" msgid "LDAP Source"
@ -2539,24 +2512,22 @@ msgstr "Asignaciones de Propiedades de Fuente de LDAP"
msgid "" msgid ""
"Unique ID used while checking if this object still exists in the directory." "Unique ID used while checking if this object still exists in the directory."
msgstr "" msgstr ""
"ID único utilizado para verificar si este objeto aún existe en el "
"directorio."
#: authentik/sources/ldap/models.py #: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection" msgid "User LDAP Source Connection"
msgstr "Conexión de Fuente LDAP de Usuario" msgstr ""
#: authentik/sources/ldap/models.py #: authentik/sources/ldap/models.py
msgid "User LDAP Source Connections" msgid "User LDAP Source Connections"
msgstr "Conexiones de Fuente LDAP de Usuario" msgstr ""
#: authentik/sources/ldap/models.py #: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connection" msgid "Group LDAP Source Connection"
msgstr "Conexión de Fuente LDAP de Grupo" msgstr ""
#: authentik/sources/ldap/models.py #: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connections" msgid "Group LDAP Source Connections"
msgstr "Conexiones de Fuente LDAP de Grupo" msgstr ""
#: authentik/sources/ldap/signals.py #: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity." msgid "Password does not match Active Directory Complexity."
@ -2568,11 +2539,11 @@ msgstr "No se recibió ningún token."
#: authentik/sources/oauth/models.py #: authentik/sources/oauth/models.py
msgid "HTTP Basic Authentication" msgid "HTTP Basic Authentication"
msgstr "Autenticación Básica HTTP" msgstr ""
#: authentik/sources/oauth/models.py #: authentik/sources/oauth/models.py
msgid "Include the client ID and secret as request parameters" msgid "Include the client ID and secret as request parameters"
msgstr "Incluir el ID de cliente y el secreto como parámetros de la solicitud" msgstr ""
#: authentik/sources/oauth/models.py #: authentik/sources/oauth/models.py
msgid "Request Token URL" msgid "Request Token URL"
@ -2619,8 +2590,6 @@ msgid ""
"How to perform authentication during an authorization_code token request " "How to perform authentication during an authorization_code token request "
"flow" "flow"
msgstr "" msgstr ""
"Cómo realizar la autenticación durante un flujo de solicitud de token con "
"authorization_code"
#: authentik/sources/oauth/models.py #: authentik/sources/oauth/models.py
msgid "OAuth Source" msgid "OAuth Source"
@ -2938,7 +2907,7 @@ msgstr "Conexiones de Fuente de SAML de Grupo"
#: authentik/sources/saml/views.py #: authentik/sources/saml/views.py
#, python-brace-format #, python-brace-format
msgid "Continue to {source_name}" msgid "Continue to {source_name}"
msgstr "Continuar a {source_name}" msgstr ""
#: authentik/sources/scim/models.py #: authentik/sources/scim/models.py
msgid "SCIM Source" msgid "SCIM Source"
@ -2974,7 +2943,7 @@ msgstr "Dispositivos Duo"
#: authentik/stages/authenticator_email/models.py #: authentik/stages/authenticator_email/models.py
msgid "Email OTP" msgid "Email OTP"
msgstr "OTP por Correo Electrónico" msgstr ""
#: authentik/stages/authenticator_email/models.py #: authentik/stages/authenticator_email/models.py
#: authentik/stages/email/models.py #: authentik/stages/email/models.py
@ -2995,11 +2964,11 @@ msgstr ""
#: authentik/stages/authenticator_email/models.py #: authentik/stages/authenticator_email/models.py
msgid "Email Authenticator Setup Stage" msgid "Email Authenticator Setup Stage"
msgstr "Etapa de Configuración del Autenticador de Correo Electrónico" msgstr ""
#: authentik/stages/authenticator_email/models.py #: authentik/stages/authenticator_email/models.py
msgid "Email Authenticator Setup Stages" msgid "Email Authenticator Setup Stages"
msgstr "Etapas de Configuración del Autenticador de Correo Electrónico" msgstr ""
#: authentik/stages/authenticator_email/models.py #: authentik/stages/authenticator_email/models.py
#: authentik/stages/authenticator_email/stage.py #: authentik/stages/authenticator_email/stage.py
@ -3010,11 +2979,11 @@ msgstr ""
#: authentik/stages/authenticator_email/models.py #: authentik/stages/authenticator_email/models.py
msgid "Email Device" msgid "Email Device"
msgstr "Dispositivo de correo electrónico" msgstr "Dispositivo de Email"
#: authentik/stages/authenticator_email/models.py #: authentik/stages/authenticator_email/models.py
msgid "Email Devices" msgid "Email Devices"
msgstr "Dispositivos de correo electrónico" msgstr "Dispositivos de Email"
#: authentik/stages/authenticator_email/stage.py #: authentik/stages/authenticator_email/stage.py
#: authentik/stages/authenticator_sms/stage.py #: authentik/stages/authenticator_sms/stage.py
@ -3024,7 +2993,7 @@ msgstr "El código no coincide"
#: authentik/stages/authenticator_email/stage.py #: authentik/stages/authenticator_email/stage.py
msgid "Invalid email" msgid "Invalid email"
msgstr "Correo electrónico inválido" msgstr "Email Inválido"
#: authentik/stages/authenticator_email/templates/email/email_otp.html #: authentik/stages/authenticator_email/templates/email/email_otp.html
#: authentik/stages/email/templates/email/password_reset.html #: authentik/stages/email/templates/email/password_reset.html
@ -3044,9 +3013,6 @@ msgid ""
" Email MFA code.\n" " Email MFA code.\n"
" " " "
msgstr "" msgstr ""
"\n"
" Código MFA por correo electrónico.\n"
" "
#: authentik/stages/authenticator_email/templates/email/email_otp.html #: authentik/stages/authenticator_email/templates/email/email_otp.html
#, python-format #, python-format
@ -3056,8 +3022,7 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
" Si no solicitaste este código, por favor ignora este correo. El código anterior es válido por %(expires)s.\n" "Si no solicitaste este código, por favor ignora este correo. El código anterior es válido por %(expires)s."
" "
#: authentik/stages/authenticator_email/templates/email/email_otp.txt #: authentik/stages/authenticator_email/templates/email/email_otp.txt
#: authentik/stages/email/templates/email/password_reset.txt #: authentik/stages/email/templates/email/password_reset.txt
@ -3070,8 +3035,6 @@ msgid ""
"\n" "\n"
"Email MFA code\n" "Email MFA code\n"
msgstr "" msgstr ""
"\n"
"Código MFA por correo electrónico\n"
#: authentik/stages/authenticator_email/templates/email/email_otp.txt #: authentik/stages/authenticator_email/templates/email/email_otp.txt
#, python-format #, python-format
@ -3313,8 +3276,8 @@ msgstr "No se pudo validar el token"
msgid "" msgid ""
"Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)." "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "" msgstr ""
"Desfase después del cual expira el consentimiento. (Formato: " "Compensación después de la cual caduca el consentimiento. (Formato: horas = "
"hours=1;minutes=2;seconds=3)." "1; minutos = 2; segundos = 3)."
#: authentik/stages/consent/models.py #: authentik/stages/consent/models.py
msgid "Consent Stage" msgid "Consent Stage"
@ -3334,7 +3297,7 @@ msgstr "Consentimientos del usuario"
#: authentik/stages/consent/stage.py #: authentik/stages/consent/stage.py
msgid "Invalid consent token, re-showing prompt" msgid "Invalid consent token, re-showing prompt"
msgstr "Token de consentimiento inválido, mostrando el aviso nuevamente" msgstr ""
#: authentik/stages/deny/models.py #: authentik/stages/deny/models.py
msgid "Deny Stage" msgid "Deny Stage"
@ -3354,11 +3317,11 @@ msgstr "Etapas ficticias"
#: authentik/stages/email/flow.py #: authentik/stages/email/flow.py
msgid "Continue to confirm this email address." msgid "Continue to confirm this email address."
msgstr "Continúa para confirmar esta dirección de correo electrónico." msgstr ""
#: authentik/stages/email/flow.py #: authentik/stages/email/flow.py
msgid "Link was already used, please request a new link." msgid "Link was already used, please request a new link."
msgstr "El enlace ya fue utilizado, por favor, solícita uno nuevo." msgstr ""
#: authentik/stages/email/models.py #: authentik/stages/email/models.py
msgid "Password Reset" msgid "Password Reset"
@ -3482,8 +3445,7 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
" Si no solicitaste un cambio de contraseña, por favor ignora este correo. El enlace anterior es válido por %(expires)s.\n" "Si no solicitaste un cambio de contraseña, por favor ignora este correo. El enlace anterior es válido por %(expires)s."
" "
#: authentik/stages/email/templates/email/password_reset.txt #: authentik/stages/email/templates/email/password_reset.txt
msgid "" msgid ""
@ -3567,26 +3529,24 @@ msgid ""
"Show the user the 'Remember me on this device' toggle, allowing repeat users" "Show the user the 'Remember me on this device' toggle, allowing repeat users"
" to skip straight to entering their password." " to skip straight to entering their password."
msgstr "" msgstr ""
"Mostrar al usuario la opción \"Recordarme en este dispositivo\", permitiendo"
" que los usuarios recurrentes pasen directamente a ingresar su contraseña."
#: authentik/stages/identification/models.py #: authentik/stages/identification/models.py
msgid "Optional enrollment flow, which is linked at the bottom of the page." msgid "Optional enrollment flow, which is linked at the bottom of the page."
msgstr "" msgstr ""
"Flujo de inscripción opcional, que se enlaza en la parte inferior de la " "Flujo de inscripción opcional, que está vinculado en la parte inferior de la"
"página." " página."
#: authentik/stages/identification/models.py #: authentik/stages/identification/models.py
msgid "Optional recovery flow, which is linked at the bottom of the page." msgid "Optional recovery flow, which is linked at the bottom of the page."
msgstr "" msgstr ""
"Flujo de recuperación opcional, que se enlaza en la parte inferior de la " "Flujo de recuperación opcional, que está vinculado en la parte inferior de "
"página." "la página."
#: authentik/stages/identification/models.py #: authentik/stages/identification/models.py
msgid "Optional passwordless flow, which is linked at the bottom of the page." msgid "Optional passwordless flow, which is linked at the bottom of the page."
msgstr "" msgstr ""
"Flujo opcional sin contraseña, que se enlaza en la parte inferior de la " "Flujo sin contraseña opcional, el cual está vinculado en la parte inferior "
"página." "de la página."
#: authentik/stages/identification/models.py #: authentik/stages/identification/models.py
msgid "Specify which sources should be shown." msgid "Specify which sources should be shown."
@ -3820,11 +3780,11 @@ msgstr "Las contraseñas no coinciden."
#: authentik/stages/redirect/api.py #: authentik/stages/redirect/api.py
msgid "Target URL should be present when mode is Static." msgid "Target URL should be present when mode is Static."
msgstr "La URL de destino debe estar presente cuando el modo es Estático." msgstr ""
#: authentik/stages/redirect/api.py #: authentik/stages/redirect/api.py
msgid "Target Flow should be present when mode is Flow." msgid "Target Flow should be present when mode is Flow."
msgstr "El Flujo de Destino debe estar presente cuando el modo es Flujo." msgstr ""
#: authentik/stages/redirect/models.py #: authentik/stages/redirect/models.py
msgid "Redirect Stage" msgid "Redirect Stage"
@ -3881,6 +3841,10 @@ msgstr "Etapas de inicio de"
msgid "No Pending user to login." msgid "No Pending user to login."
msgstr "Ningún usuario pendiente para iniciar sesión." msgstr "Ningún usuario pendiente para iniciar sesión."
#: authentik/stages/user_login/stage.py
msgid "Successfully logged in!"
msgstr "¡Se ha iniciado sesión correctamente!"
#: authentik/stages/user_logout/models.py #: authentik/stages/user_logout/models.py
msgid "User Logout Stage" msgid "User Logout Stage"
msgstr "Etapa de cierre de sesión del usuario" msgstr "Etapa de cierre de sesión del usuario"
@ -3956,12 +3920,10 @@ msgstr ""
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "Reputation cannot decrease lower than this value. Zero or negative." msgid "Reputation cannot decrease lower than this value. Zero or negative."
msgstr "" msgstr ""
"La reputación no puede disminuir por debajo de este valor. Cero o negativo."
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "Reputation cannot increase higher than this value. Zero or positive." msgid "Reputation cannot increase higher than this value. Zero or positive."
msgstr "" msgstr ""
"La reputación no puede aumentar por encima de este valor. Cero o positivo."
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "The option configures the footer links on the flow executor pages." msgid "The option configures the footer links on the flow executor pages."
@ -3984,8 +3946,8 @@ msgstr "Personificación habilitada/deshabilitada globalmente."
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "Require administrators to provide a reason for impersonating a user." msgid "Require administrators to provide a reason for impersonating a user."
msgstr "" msgstr ""
"Requerir que los administradores proporcionen una razón para personificar a " "Requerir a los administradores proporcionar una razón para suplantar un "
"un usuario." "usuario."
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "Default token duration" msgid "Default token duration"
@ -3997,7 +3959,7 @@ msgstr "Longitud predeterminada del token"
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "Tenant" msgid "Tenant"
msgstr "Inquilino" msgstr "inquilino"
#: authentik/tenants/models.py #: authentik/tenants/models.py
msgid "Tenants" msgid "Tenants"

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "@goauthentik/authentik", "name": "@goauthentik/authentik",
"version": "2025.6.2", "version": "2025.6.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@goauthentik/authentik", "name": "@goauthentik/authentik",
"version": "2025.6.2", "version": "2025.6.1",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2", "@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.3.3", "prettier": "^3.3.3",

View File

@ -1,6 +1,6 @@
{ {
"name": "@goauthentik/authentik", "name": "@goauthentik/authentik",
"version": "2025.6.2", "version": "2025.6.1",
"private": true, "private": true,
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {

View File

@ -216,9 +216,9 @@
} }
}, },
"node_modules/@eslint/config-array": { "node_modules/@eslint/config-array": {
"version": "0.20.1", "version": "0.20.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
"integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/object-schema": "^2.1.6", "@eslint/object-schema": "^2.1.6",
@ -274,9 +274,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.29.0", "version": "9.28.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz",
"integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -576,17 +576,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz",
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/scope-manager": "8.34.0",
"@typescript-eslint/type-utils": "8.34.1", "@typescript-eslint/type-utils": "8.34.0",
"@typescript-eslint/utils": "8.34.1", "@typescript-eslint/utils": "8.34.0",
"@typescript-eslint/visitor-keys": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^7.0.0", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -600,7 +600,7 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.34.1", "@typescript-eslint/parser": "^8.34.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <5.9.0"
} }
@ -616,16 +616,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz",
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/scope-manager": "8.34.0",
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.34.0",
"@typescript-eslint/typescript-estree": "8.34.1", "@typescript-eslint/typescript-estree": "8.34.0",
"@typescript-eslint/visitor-keys": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -641,14 +641,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz",
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.34.1", "@typescript-eslint/tsconfig-utils": "^8.34.0",
"@typescript-eslint/types": "^8.34.1", "@typescript-eslint/types": "^8.34.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -663,14 +663,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz",
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.34.0",
"@typescript-eslint/visitor-keys": "8.34.1" "@typescript-eslint/visitor-keys": "8.34.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -681,9 +681,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz",
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -698,14 +698,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz",
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.34.1", "@typescript-eslint/typescript-estree": "8.34.0",
"@typescript-eslint/utils": "8.34.1", "@typescript-eslint/utils": "8.34.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.1.0"
}, },
@ -722,9 +722,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -736,16 +736,16 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz",
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.34.1", "@typescript-eslint/project-service": "8.34.0",
"@typescript-eslint/tsconfig-utils": "8.34.1", "@typescript-eslint/tsconfig-utils": "8.34.0",
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.34.0",
"@typescript-eslint/visitor-keys": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -765,9 +765,9 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -804,16 +804,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz",
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.7.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/scope-manager": "8.34.0",
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.34.0",
"@typescript-eslint/typescript-estree": "8.34.1" "@typescript-eslint/typescript-estree": "8.34.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -828,14 +828,14 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz",
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.34.0",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -846,9 +846,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.15.0", "version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -1554,18 +1554,18 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.29.0", "version": "9.28.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.1", "@eslint/config-array": "^0.20.0",
"@eslint/config-helpers": "^0.2.1", "@eslint/config-helpers": "^0.2.1",
"@eslint/core": "^0.14.0", "@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.29.0", "@eslint/js": "9.28.0",
"@eslint/plugin-kit": "^0.3.1", "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@ -1577,9 +1577,9 @@
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.4.0", "eslint-scope": "^8.3.0",
"eslint-visitor-keys": "^4.2.1", "eslint-visitor-keys": "^4.2.0",
"espree": "^10.4.0", "espree": "^10.3.0",
"esquery": "^1.5.0", "esquery": "^1.5.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -1792,9 +1792,9 @@
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "8.4.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
@ -1808,9 +1808,9 @@
} }
}, },
"node_modules/eslint-visitor-keys": { "node_modules/eslint-visitor-keys": {
"version": "4.2.1", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1820,14 +1820,14 @@
} }
}, },
"node_modules/espree": { "node_modules/espree": {
"version": "10.4.0", "version": "10.3.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"acorn": "^8.15.0", "acorn": "^8.14.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -4035,15 +4035,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.34.1", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz",
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.34.1", "@typescript-eslint/eslint-plugin": "8.34.0",
"@typescript-eslint/parser": "8.34.1", "@typescript-eslint/parser": "8.34.0",
"@typescript-eslint/utils": "8.34.1" "@typescript-eslint/utils": "8.34.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View File

@ -1,6 +1,6 @@
[project] [project]
name = "authentik" name = "authentik"
version = "2025.6.2" version = "2025.6.1"
description = "" description = ""
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }] authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
requires-python = "==3.13.*" requires-python = "==3.13.*"
@ -48,7 +48,7 @@ dependencies = [
"packaging==25.0", "packaging==25.0",
"paramiko==3.5.1", "paramiko==3.5.1",
"psycopg[c,pool]==3.2.9", "psycopg[c,pool]==3.2.9",
"pydantic==2.11.7", "pydantic==2.11.5",
"pydantic-scim==0.0.8", "pydantic-scim==0.0.8",
"pyjwt==2.10.1", "pyjwt==2.10.1",
"pyrad==2.4", "pyrad==2.4",
@ -68,7 +68,7 @@ dependencies = [
"urllib3<3", "urllib3<3",
"uvicorn[standard]==0.34.3", "uvicorn[standard]==0.34.3",
"watchdog==6.0.0", "watchdog==6.0.0",
"webauthn==2.6.0", "webauthn==2.5.2",
"wsproto==1.2.0", "wsproto==1.2.0",
"xmlsec==1.3.15", "xmlsec==1.3.15",
"zxcvbn==4.5.0", "zxcvbn==4.5.0",

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: authentik title: authentik
version: 2025.6.2 version: 2025.6.1
description: Making authentication simple. description: Making authentication simple.
contact: contact:
email: hello@goauthentik.io email: hello@goauthentik.io
@ -22454,17 +22454,6 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
- in: query
name: default_name_id_policy
schema:
type: string
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
- in: query - in: query
name: default_relay_state name: default_relay_state
schema: schema:
@ -29681,7 +29670,6 @@ paths:
enum: enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName - urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName - urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient - urn:oasis:names:tc:SAML:2.0:nameid-format:transient
@ -48757,6 +48745,14 @@ components:
- mode - mode
- name - name
- user_attribute - user_attribute
NameIdPolicyEnum:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
type: string
NetworkBindingEnum: NetworkBindingEnum:
enum: enum:
- no_binding - no_binding
@ -54505,8 +54501,6 @@ components:
default_relay_state: default_relay_state:
type: string type: string
description: Default relay_state value for IDP-initiated logins description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
PatchedSAMLSourcePropertyMappingRequest: PatchedSAMLSourcePropertyMappingRequest:
type: object type: object
description: SAMLSourcePropertyMapping Serializer description: SAMLSourcePropertyMapping Serializer
@ -54600,7 +54594,7 @@ components:
be a security risk, as no validation of the request ID is done. be a security risk, as no validation of the request ID is done.
name_id_policy: name_id_policy:
allOf: allOf:
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum' - $ref: '#/components/schemas/NameIdPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent. no Policy is sent.
binding_type: binding_type:
@ -57311,15 +57305,6 @@ components:
required: required:
- download_url - download_url
- metadata - metadata
SAMLNameIDPolicyEnum:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
type: string
SAMLPropertyMapping: SAMLPropertyMapping:
type: object type: object
description: SAMLPropertyMapping Serializer description: SAMLPropertyMapping Serializer
@ -57537,8 +57522,6 @@ components:
default_relay_state: default_relay_state:
type: string type: string
description: Default relay_state value for IDP-initiated logins description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
url_download_metadata: url_download_metadata:
type: string type: string
description: Get metadata download URL description: Get metadata download URL
@ -57711,8 +57694,6 @@ components:
default_relay_state: default_relay_state:
type: string type: string
description: Default relay_state value for IDP-initiated logins description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
required: required:
- acs_url - acs_url
- authorization_flow - authorization_flow
@ -57821,7 +57802,7 @@ components:
be a security risk, as no validation of the request ID is done. be a security risk, as no validation of the request ID is done.
name_id_policy: name_id_policy:
allOf: allOf:
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum' - $ref: '#/components/schemas/NameIdPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent. no Policy is sent.
binding_type: binding_type:
@ -58011,7 +57992,7 @@ components:
be a security risk, as no validation of the request ID is done. be a security risk, as no validation of the request ID is done.
name_id_policy: name_id_policy:
allOf: allOf:
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum' - $ref: '#/components/schemas/NameIdPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent. no Policy is sent.
binding_type: binding_type:

View File

@ -7,7 +7,7 @@ services:
network_mode: host network_mode: host
restart: always restart: always
mailpit: mailpit:
image: docker.io/axllent/mailpit:v1.26.1 image: docker.io/axllent/mailpit:v1.26.0
ports: ports:
- 1025:1025 - 1025:1025
- 8025:8025 - 8025:8025

18
uv.lock generated
View File

@ -165,7 +165,7 @@ wheels = [
[[package]] [[package]]
name = "authentik" name = "authentik"
version = "2025.6.2" version = "2025.6.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "argon2-cffi" }, { name = "argon2-cffi" },
@ -309,7 +309,7 @@ requires-dist = [
{ name = "packaging", specifier = "==25.0" }, { name = "packaging", specifier = "==25.0" },
{ name = "paramiko", specifier = "==3.5.1" }, { name = "paramiko", specifier = "==3.5.1" },
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" }, { name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" },
{ name = "pydantic", specifier = "==2.11.7" }, { name = "pydantic", specifier = "==2.11.5" },
{ name = "pydantic-scim", specifier = "==0.0.8" }, { name = "pydantic-scim", specifier = "==0.0.8" },
{ name = "pyjwt", specifier = "==2.10.1" }, { name = "pyjwt", specifier = "==2.10.1" },
{ name = "pyrad", specifier = "==2.4" }, { name = "pyrad", specifier = "==2.4" },
@ -329,7 +329,7 @@ requires-dist = [
{ name = "urllib3", specifier = "<3" }, { name = "urllib3", specifier = "<3" },
{ name = "uvicorn", extras = ["standard"], specifier = "==0.34.3" }, { name = "uvicorn", extras = ["standard"], specifier = "==0.34.3" },
{ name = "watchdog", specifier = "==6.0.0" }, { name = "watchdog", specifier = "==6.0.0" },
{ name = "webauthn", specifier = "==2.6.0" }, { name = "webauthn", specifier = "==2.5.2" },
{ name = "wsproto", specifier = "==1.2.0" }, { name = "wsproto", specifier = "==1.2.0" },
{ name = "xmlsec", specifier = "==1.3.15" }, { name = "xmlsec", specifier = "==1.3.15" },
{ name = "zxcvbn", specifier = "==4.5.0" }, { name = "zxcvbn", specifier = "==4.5.0" },
@ -2463,7 +2463,7 @@ wheels = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.11.7" version = "2.11.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "annotated-types" }, { name = "annotated-types" },
@ -2471,9 +2471,9 @@ dependencies = [
{ name = "typing-extensions" }, { name = "typing-extensions" },
{ name = "typing-inspection" }, { name = "typing-inspection" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@ -3391,7 +3391,7 @@ wheels = [
[[package]] [[package]]
name = "webauthn" name = "webauthn"
version = "2.6.0" version = "2.5.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asn1crypto" }, { name = "asn1crypto" },
@ -3399,9 +3399,9 @@ dependencies = [
{ name = "cryptography" }, { name = "cryptography" },
{ name = "pyopenssl" }, { name = "pyopenssl" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/63/38/5792cb2034673c162a721df0ad65825699516ee0c938a65670ad3cdabf6c/webauthn-2.6.0.tar.gz", hash = "sha256:13cf5b009a64cef569599ffecf24550df1d7c0cd4fbaea870f937148484a80b4", size = 123608, upload-time = "2025-06-16T22:25:26.76Z" } sdist = { url = "https://files.pythonhosted.org/packages/8d/92/8d2a4eec83d8e7feacdaad37c6eb6eb922100cecce5c14a41d8069a59a03/webauthn-2.5.2.tar.gz", hash = "sha256:09c13dfc1c68c810f32fa4d89b1d37acb9f9ae9091c9d7019e313be4525a95ef", size = 124114, upload-time = "2025-03-07T19:44:05.243Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/56/c5/b1bba7f6a50caca77f37003e098f48f8dc68d990aba8a03ac8376016430b/webauthn-2.6.0-py3-none-any.whl", hash = "sha256:459973eb5780c1f41bec42b682acf587456b185733398a0b99a0714705b79447", size = 71189, upload-time = "2025-06-16T22:25:25.535Z" }, { url = "https://files.pythonhosted.org/packages/7f/fe/f6ae41de9f383439e30b303a67f6f45d2fceabedaedc34c62f74d58c5c73/webauthn-2.5.2-py3-none-any.whl", hash = "sha256:44246e496e617eb5e2f51165046b9f0197fcdf470f69cd6734061a27ba365f8e", size = 71624, upload-time = "2025-03-07T19:44:03.728Z" },
] ]
[[package]] [[package]]

1707
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -93,7 +93,7 @@
"@floating-ui/dom": "^1.6.11", "@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.7.11", "@formatjs/intl-listformat": "^7.7.11",
"@fortawesome/fontawesome-free": "^6.7.2", "@fortawesome/fontawesome-free": "^6.7.2",
"@goauthentik/api": "^2025.6.2-1750112513", "@goauthentik/api": "^2025.6.1-1749515784",
"@lit/context": "^1.1.2", "@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2", "@lit/localize": "^0.12.2",
"@lit/reactive-element": "^2.0.4", "@lit/reactive-element": "^2.0.4",
@ -102,9 +102,10 @@
"@open-wc/lit-helpers": "^0.7.0", "@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^4.1.0", "@patternfly/elements": "^4.1.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^9.30.0", "@sentry/browser": "^9.28.1",
"@spotlightjs/spotlight": "^3.0.0", "@spotlightjs/spotlight": "^3.0.0",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"change-case": "^5.4.4", "change-case": "^5.4.4",
"chart.js": "^4.4.9", "chart.js": "^4.4.9",
"chartjs-adapter-date-fns": "^3.0.0", "chartjs-adapter-date-fns": "^3.0.0",
@ -136,7 +137,6 @@
"trusted-types": "^2.0.0", "trusted-types": "^2.0.0",
"ts-pattern": "^5.7.1", "ts-pattern": "^5.7.1",
"unist-util-visit": "^5.0.0", "unist-util-visit": "^5.0.0",
"webauthn-polyfills": "^0.1.7",
"webcomponent-qr-code": "^1.2.0", "webcomponent-qr-code": "^1.2.0",
"yaml": "^2.8.0" "yaml": "^2.8.0"
}, },
@ -170,16 +170,16 @@
"@types/react-dom": "^19.1.5", "@types/react-dom": "^19.1.5",
"@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/eslint-plugin": "^8.8.0",
"@typescript-eslint/parser": "^8.8.0", "@typescript-eslint/parser": "^8.8.0",
"@wdio/browser-runner": "9.15", "@wdio/browser-runner": "9.4",
"@wdio/cli": "9.15", "@wdio/cli": "9.4",
"@wdio/spec-reporter": "^9.15.0", "@wdio/spec-reporter": "^9.1.2",
"@web/test-runner": "^0.20.2", "@web/test-runner": "^0.20.2",
"chromedriver": "^136.0.3", "chromedriver": "^136.0.3",
"esbuild": "^0.25.5", "esbuild": "^0.25.5",
"esbuild-plugin-copy": "^2.1.1", "esbuild-plugin-copy": "^2.1.1",
"esbuild-plugin-polyfill-node": "^0.3.0", "esbuild-plugin-polyfill-node": "^0.3.0",
"esbuild-plugins-node-modules-polyfill": "^1.7.0", "esbuild-plugins-node-modules-polyfill": "^1.7.0",
"eslint": "^9.29.0", "eslint": "^9.28.0",
"eslint-plugin-lit": "^2.1.1", "eslint-plugin-lit": "^2.1.1",
"eslint-plugin-wc": "^3.0.1", "eslint-plugin-wc": "^3.0.1",
"github-slugger": "^2.0.0", "github-slugger": "^2.0.0",
@ -194,7 +194,7 @@
"storybook-addon-mock": "^5.0.0", "storybook-addon-mock": "^5.0.0",
"turnstile-types": "^1.2.3", "turnstile-types": "^1.2.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.34.1", "typescript-eslint": "^8.34.0",
"vite-plugin-lit-css": "^2.0.0", "vite-plugin-lit-css": "^2.0.0",
"vite-tsconfig-paths": "^5.0.1", "vite-tsconfig-paths": "^5.0.1",
"wireit": "^0.14.12" "wireit": "^0.14.12"

View File

@ -14,7 +14,7 @@ declare module "module" {
* const relativeDirname = dirname(fileURLToPath(import.meta.url)); * const relativeDirname = dirname(fileURLToPath(import.meta.url));
* ``` * ```
*/ */
// eslint-disable-next-line no-var
var __dirname: string; var __dirname: string;
} }
} }

View File

@ -11,11 +11,11 @@
}, },
"dependencies": { "dependencies": {
"@goauthentik/api": "^2024.6.0-1719577139", "@goauthentik/api": "^2024.6.0-1719577139",
"base64-js": "^1.5.1",
"bootstrap": "^4.6.1", "bootstrap": "^4.6.1",
"formdata-polyfill": "^4.0.10", "formdata-polyfill": "^4.0.10",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"weakmap-polyfill": "^2.0.4", "weakmap-polyfill": "^2.0.4"
"webauthn-polyfills": "^0.1.7"
}, },
"devDependencies": { "devDependencies": {
"@goauthentik/core": "^1.0.0", "@goauthentik/core": "^1.0.0",

View File

@ -1,7 +1,7 @@
import { fromByteArray } from "base64-js";
import "formdata-polyfill"; import "formdata-polyfill";
import $ from "jquery"; import $ from "jquery";
import "weakmap-polyfill"; import "weakmap-polyfill";
import "webauthn-polyfills";
import { import {
type AuthenticatorValidationChallenge, type AuthenticatorValidationChallenge,
@ -257,9 +257,47 @@ class AutosubmitStage extends Stage<AutosubmitChallenge> {
} }
} }
export interface Assertion {
id: string;
rawId: string;
type: string;
registrationClientExtensions: string;
response: {
clientDataJSON: string;
attestationObject: string;
};
}
export interface AuthAssertion {
id: string;
rawId: string;
type: string;
assertionClientExtensions: string;
response: {
clientDataJSON: string;
authenticatorData: string;
signature: string;
userHandle: string | null;
};
}
class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> { class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> {
deviceChallenge?: DeviceChallenge; deviceChallenge?: DeviceChallenge;
b64enc(buf: Uint8Array): string {
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
b64RawEnc(buf: Uint8Array): string {
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_");
}
u8arr(input: string): Uint8Array {
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
c.charCodeAt(0),
);
}
checkWebAuthnSupport(): boolean { checkWebAuthnSupport(): boolean {
if ("credentials" in navigator) { if ("credentials" in navigator) {
return true; return true;
@ -272,6 +310,98 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
return false; return false;
} }
/**
* Transforms items in the credentialCreateOptions generated on the server
* into byte arrays expected by the navigator.credentials.create() call
*/
transformCredentialCreateOptions(
credentialCreateOptions: PublicKeyCredentialCreationOptions,
userId: string,
): PublicKeyCredentialCreationOptions {
const user = credentialCreateOptions.user;
// Because json can't contain raw bytes, the server base64-encodes the User ID
// So to get the base64 encoded byte array, we first need to convert it to a regular
// string, then a byte array, re-encode it and wrap that in an array.
const stringId = decodeURIComponent(window.atob(userId));
user.id = this.u8arr(this.b64enc(this.u8arr(stringId)));
const challenge = this.u8arr(credentialCreateOptions.challenge.toString());
return Object.assign({}, credentialCreateOptions, {
challenge,
user,
});
}
/**
* Transforms the binary data in the credential into base64 strings
* for posting to the server.
* @param {PublicKeyCredential} newAssertion
*/
transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion {
const attObj = new Uint8Array(
(newAssertion.response as AuthenticatorAttestationResponse).attestationObject,
);
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
const rawId = new Uint8Array(newAssertion.rawId);
const registrationClientExtensions = newAssertion.getClientExtensionResults();
return {
id: newAssertion.id,
rawId: this.b64enc(rawId),
type: newAssertion.type,
registrationClientExtensions: JSON.stringify(registrationClientExtensions),
response: {
clientDataJSON: this.b64enc(clientDataJSON),
attestationObject: this.b64enc(attObj),
},
};
}
transformCredentialRequestOptions(
credentialRequestOptions: PublicKeyCredentialRequestOptions,
): PublicKeyCredentialRequestOptions {
const challenge = this.u8arr(credentialRequestOptions.challenge.toString());
const allowCredentials = (credentialRequestOptions.allowCredentials || []).map(
(credentialDescriptor) => {
const id = this.u8arr(credentialDescriptor.id.toString());
return Object.assign({}, credentialDescriptor, { id });
},
);
return Object.assign({}, credentialRequestOptions, {
challenge,
allowCredentials,
});
}
/**
* Encodes the binary data in the assertion into strings for posting to the server.
* @param {PublicKeyCredential} newAssertion
*/
transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion {
const response = newAssertion.response as AuthenticatorAssertionResponse;
const authData = new Uint8Array(response.authenticatorData);
const clientDataJSON = new Uint8Array(response.clientDataJSON);
const rawId = new Uint8Array(newAssertion.rawId);
const sig = new Uint8Array(response.signature);
const assertionClientExtensions = newAssertion.getClientExtensionResults();
return {
id: newAssertion.id,
rawId: this.b64enc(rawId),
type: newAssertion.type,
assertionClientExtensions: JSON.stringify(assertionClientExtensions),
response: {
clientDataJSON: this.b64RawEnc(clientDataJSON),
signature: this.b64RawEnc(sig),
authenticatorData: this.b64RawEnc(authData),
userHandle: null,
},
};
}
render() { render() {
if (this.challenge.deviceChallenges.length === 1) { if (this.challenge.deviceChallenges.length === 1) {
this.deviceChallenge = this.challenge.deviceChallenges[0]; this.deviceChallenge = this.challenge.deviceChallenges[0];
@ -375,8 +505,8 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
`); `);
navigator.credentials navigator.credentials
.get({ .get({
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON( publicKey: this.transformCredentialRequestOptions(
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptionsJSON, this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptions,
), ),
}) })
.then((assertion) => { .then((assertion) => {
@ -384,9 +514,15 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
throw new Error("No assertion"); throw new Error("No assertion");
} }
try { try {
// we now have an authentication assertion! encode the byte arrays contained
// in the assertion data as strings for posting to the server
const transformedAssertionForServer = this.transformAssertionForServer(
assertion as PublicKeyCredential,
);
// post the assertion to the server for verification. // post the assertion to the server for verification.
this.executor.submit({ this.executor.submit({
webauthn: (assertion as PublicKeyCredential).toJSON(), webauthn: transformedAssertionForServer,
}); });
} catch (err) { } catch (err) {
throw new Error(`Error when validating assertion on server: ${err}`); throw new Error(`Error when validating assertion on server: ${err}`);

View File

@ -88,8 +88,7 @@ export class RecentEventsCard extends Table<Event> {
} }
return super.renderEmpty( return super.renderEmpty(
html`<ak-empty-state html`<ak-empty-state header=${msg("No Events found.")}>
><span slot="header">${msg("No Events found.")}</span>
<div slot="body">${msg("No matching events could be found.")}</div> <div slot="body">${msg("No matching events could be found.")}</div>
</ak-empty-state>`, </ak-empty-state>`,
); );

View File

@ -5,7 +5,6 @@ import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
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-slug-input";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input"; import "@goauthentik/components/ak-textarea-input";
@ -131,14 +130,14 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
required required
help=${msg("Application's display Name.")} help=${msg("Application's display Name.")}
></ak-text-input> ></ak-text-input>
<ak-slug-input <ak-text-input
name="slug" name="slug"
value=${ifDefined(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.")}
input-hint="code" input-hint="code"
></ak-slug-input> ></ak-text-input>
<ak-text-input <ak-text-input
name="group" name="group"
value=${ifDefined(this.instance?.group)} value=${ifDefined(this.instance?.group)}

View File

@ -117,11 +117,13 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
?invalid=${this.errors.has("name")} ?invalid=${this.errors.has("name")}
.errorMessages=${errors.name ?? this.errorMessages("name")} .errorMessages=${errors.name ?? this.errorMessages("name")}
help=${msg("Application's display Name.")} help=${msg("Application's display Name.")}
id="ak-application-wizard-details-name"
></ak-text-input> ></ak-text-input>
<ak-slug-input <ak-slug-input
name="slug" name="slug"
value=${ifDefined(app.slug)} value=${ifDefined(app.slug)}
label=${msg("Slug")} label=${msg("Slug")}
source="#ak-application-wizard-details-name"
required required
?invalid=${errors.slug ?? this.errors.has("slug")} ?invalid=${errors.slug ?? this.errors.has("slug")}
.errorMessages=${this.errorMessages("slug")} .errorMessages=${this.errorMessages("slug")}

View File

@ -115,8 +115,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
.columns=${COLUMNS} .columns=${COLUMNS}
.content=${[]} .content=${[]}
></ak-select-table> ></ak-select-table>
<ak-empty-state icon="pf-icon-module" <ak-empty-state header=${msg("No bound policies.")} icon="pf-icon-module">
><span slot="header">${msg("No bound policies.")} </span>
<div slot="body">${msg("No policies are currently bound to this object.")}</div> <div slot="body">${msg("No policies are currently bound to this object.")}</div>
<div slot="primary"> <div slot="primary">
<button <button

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-secret-textarea-input.js"; import "@goauthentik/components/ak-private-textarea-input.js";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -46,7 +46,7 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-textarea-input <ak-private-textarea-input
label=${msg("Certificate")} label=${msg("Certificate")}
name="certificateData" name="certificateData"
input-hint="code" input-hint="code"
@ -54,8 +54,8 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
required required
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
help=${msg("PEM-encoded Certificate data.")} help=${msg("PEM-encoded Certificate data.")}
></ak-secret-textarea-input> ></ak-private-textarea-input>
<ak-secret-textarea-input <ak-private-textarea-input
label=${msg("Private Key")} label=${msg("Private Key")}
name="keyData" name="keyData"
input-hint="code" input-hint="code"
@ -63,7 +63,7 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
help=${msg( help=${msg(
"Optional Private Key. If this is set, you can use this keypair for encryption.", "Optional Private Key. If this is set, you can use this keypair for encryption.",
)} )}
></ak-secret-textarea-input>`; ></ak-private-textarea-input>`;
} }
} }

View File

@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants"; import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants";
import "@goauthentik/components/ak-secret-textarea-input.js"; import "@goauthentik/components/ak-private-textarea-input.js";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -62,13 +62,13 @@ export class EnterpriseLicenseForm extends ModelForm<License, string> {
value="${ifDefined(this.installID)}" value="${ifDefined(this.installID)}"
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-textarea-input <ak-private-textarea-input
name="key" name="key"
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
label=${msg("License key")} label=${msg("License key")}
input-hint="code" input-hint="code"
> >
</ak-secret-textarea-input>`; </ak-private-textarea-input>`;
} }
} }

View File

@ -135,8 +135,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
renderEmpty(): TemplateResult { renderEmpty(): TemplateResult {
return super.renderEmpty( return super.renderEmpty(
html`<ak-empty-state icon="pf-icon-module"> html`<ak-empty-state header=${msg("No Stages bound")} icon="pf-icon-module">
<span slot="header">${msg("No Stages bound")}</span>
<div slot="body">${msg("No stages are currently bound to this flow.")}</div> <div slot="body">${msg("No stages are currently bound to this flow.")}</div>
<div slot="primary"> <div slot="primary">
<ak-stage-wizard <ak-stage-wizard

View File

@ -3,7 +3,6 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util
import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes"; import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes";
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum"; import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-slug-input.js";
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";
@ -92,16 +91,17 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
/> />
<p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p> <p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
<ak-slug-input <input
name="slug" type="text"
value=${ifDefined(this.instance?.slug)} value="${ifDefined(this.instance?.slug)}"
label=${msg("Slug")} class="pf-c-form-control pf-m-monospace"
required autocomplete="off"
help=${msg("Visible in the URL.")} spellcheck="false"
input-hint="code" required
></ak-slug-input> />
<p class="pf-c-form__helper-text">${msg("Visible in the URL.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Designation")} required name="designation"> <ak-form-element-horizontal label=${msg("Designation")} required name="designation">
<select class="pf-c-form-control"> <select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.designation === undefined}> <option value="" ?selected=${this.instance?.designation === undefined}>

View File

@ -198,8 +198,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
renderEmpty(): TemplateResult { renderEmpty(): TemplateResult {
return super.renderEmpty( return super.renderEmpty(
html`<ak-empty-state icon="pf-icon-module" html`<ak-empty-state header=${msg("No Policies bound.")} icon="pf-icon-module">
><span slot="header">${msg("No Policies bound.")}</span>
<div slot="body">${msg("No policies are currently bound to this object.")}</div> <div slot="body">${msg("No policies are currently bound to this object.")}</div>
<div slot="primary"> <div slot="primary">
<ak-policy-wizard <ak-policy-wizard

View File

@ -4,7 +4,6 @@ import {
propertyMappingsSelector, propertyMappingsSelector,
} from "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderFormHelpers.js"; } from "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderFormHelpers.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-hidden-text-input";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
@ -69,15 +68,21 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
${msg("Client ID for the app registration.")} ${msg("Client ID for the app registration.")}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-hidden-text-input <ak-form-element-horizontal
name="clientSecret"
label=${msg("Client Secret")} label=${msg("Client Secret")}
value="${this.instance?.clientSecret ?? ""}"
input-hint="code"
required required
.help=${msg("Client secret for the app registration.")} name="clientSecret"
> >
</ak-hidden-text-input> <input
type="text"
value="${this.instance?.clientSecret ?? ""}"
class="pf-c-form-control pf-m-monospace"
required
/>
<p class="pf-c-form__helper-text">
${msg("Client secret for the app registration.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Tenant ID")} required name="tenantId"> <ak-form-element-horizontal label=${msg("Tenant ID")} required name="tenantId">
<input <input
type="text" type="text"

View File

@ -5,7 +5,6 @@ import {
akOAuthRedirectURIInput, akOAuthRedirectURIInput,
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI"; } from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-hidden-text-input";
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-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";
@ -167,16 +166,17 @@ export function renderForm(
input-hint="code" input-hint="code"
> >
</ak-text-input> </ak-text-input>
<ak-hidden-text-input <ak-text-input
name="clientSecret" name="clientSecret"
label=${msg("Client Secret")} label=${msg("Client Secret")}
value="${provider?.clientSecret ?? randomString(128, ascii_letters + digits)}" value="${provider?.clientSecret ?? randomString(128, ascii_letters + digits)}"
input-hint="code" input-hint="code"
?hidden=${!showClientSecret} ?hidden=${!showClientSecret}
> >
</ak-hidden-text-input> </ak-text-input>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Redirect URIs/Origins (RegEx)")} label=${msg("Redirect URIs/Origins (RegEx)")}
required
name="redirectUris" name="redirectUris"
> >
<ak-array-input <ak-array-input

View File

@ -1,8 +1,6 @@
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-hidden-text-input";
import "@goauthentik/components/ak-text-input";
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";
@ -76,14 +74,14 @@ export function renderForm(
<ak-form-group expanded> <ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span> <span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-hidden-text-input <ak-text-input
name="sharedSecret" name="sharedSecret"
label=${msg("Shared secret")} label=${msg("Shared secret")}
.errorMessages=${errors?.sharedSecret ?? []} .errorMessages=${errors?.sharedSecret ?? []}
value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)} value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)}
required required
input-hint="code" input-hint="code"
></ak-hidden-text-input> ></ak-text-input>
<ak-text-input <ak-text-input
name="clientNetworks" name="clientNetworks"
label=${msg("Client Networks")} label=${msg("Client Networks")}

View File

@ -16,7 +16,6 @@ import {
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
PropertymappingsApi, PropertymappingsApi,
PropertymappingsProviderSamlListRequest, PropertymappingsProviderSamlListRequest,
SAMLNameIDPolicyEnum,
SAMLPropertyMapping, SAMLPropertyMapping,
SAMLProvider, SAMLProvider,
SpBindingEnum, SpBindingEnum,
@ -317,54 +316,6 @@ export function renderForm(
"When using IDP-initiated logins, the relay state will be set to this value.", "When using IDP-initiated logins, the relay state will be set to this value.",
)} )}
></ak-text-input> ></ak-text-input>
<ak-form-element-horizontal
label=${msg("Default NameID Policy")}
required
name="defaultNameIdPolicy"
>
<select class="pf-c-form-control">
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
>
${msg("Persistent")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
>
${msg("Email address")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
>
${msg("Windows")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
>
${msg("X509 Subject")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
>
${msg("Transient")}
</option>
</select>
<p class="pf-c-form__helper-text">
${msg(
"Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).",
)}
</p>
</ak-form-element-horizontal>
<ak-radio-input <ak-radio-input
name="digestAlgorithm" name="digestAlgorithm"

View File

@ -1,5 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-hidden-text-input";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -51,7 +50,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
> >
</ak-switch-input> </ak-switch-input>
<ak-hidden-text-input <ak-text-input
name="token" name="token"
label=${msg("Token")} label=${msg("Token")}
value="${provider?.token ?? ""}" value="${provider?.token ?? ""}"
@ -61,7 +60,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
"Token to authenticate with. Currently only bearer authentication is supported.", "Token to authenticate with. Currently only bearer authentication is supported.",
)} )}
input-hint="code" input-hint="code"
></ak-hidden-text-input> ></ak-text-input>
<ak-radio-input <ak-radio-input
name="compatibilityMode" name="compatibilityMode"
label=${msg("Compatibility Mode")} label=${msg("Compatibility Mode")}

View File

@ -7,9 +7,8 @@ import {
UserMatchingModeToLabel, UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils"; } 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 "@goauthentik/components/ak-secret-text-input.js"; import "@goauthentik/components/ak-private-text-input.js";
import "@goauthentik/components/ak-secret-textarea-input.js"; import "@goauthentik/components/ak-private-textarea-input.js";
import "@goauthentik/components/ak-slug-input.js";
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";
@ -88,13 +87,12 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
value=${ifDefined(this.instance?.name)} value=${ifDefined(this.instance?.name)}
required required
></ak-text-input> ></ak-text-input>
<ak-slug-input <ak-text-input
name="slug" name="slug"
value=${ifDefined(this.instance?.slug)}
label=${msg("Slug")} label=${msg("Slug")}
value=${ifDefined(this.instance?.slug)}
required required
input-hint="code" ></ak-text-input>
></ak-slug-input>
<ak-switch-input <ak-switch-input
name="enabled" name="enabled"
?checked=${this.instance?.enabled ?? true} ?checked=${this.instance?.enabled ?? true}
@ -250,22 +248,22 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
value=${ifDefined(this.instance?.syncPrincipal)} value=${ifDefined(this.instance?.syncPrincipal)}
help=${msg("Principal used to authenticate to the KDC for syncing.")} help=${msg("Principal used to authenticate to the KDC for syncing.")}
></ak-text-input> ></ak-text-input>
<ak-secret-text-input <ak-private-text-input
name="syncPassword" name="syncPassword"
label=${msg("Sync password")} label=${msg("Sync password")}
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
help=${msg( help=${msg(
"Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.", "Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.",
)} )}
></ak-secret-text-input> ></ak-private-text-input>
<ak-secret-textarea-input <ak-private-textarea-input
name="syncKeytab" name="syncKeytab"
label=${msg("Sync keytab")} label=${msg("Sync keytab")}
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
help=${msg( help=${msg(
"Keytab used to authenticate to the KDC for syncing. Optional if Sync password or Sync credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.", "Keytab used to authenticate to the KDC for syncing. Optional if Sync password or Sync credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
)} )}
></ak-secret-textarea-input> ></ak-private-textarea-input>
<ak-text-input <ak-text-input
name="syncCcache" name="syncCcache"
label=${msg("Sync credentials cache")} label=${msg("Sync credentials cache")}
@ -287,14 +285,14 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
"Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain", "Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain",
)} )}
></ak-text-input> ></ak-text-input>
<ak-secret-textarea-input <ak-private-textarea-input
name="spnegoKeytab" name="spnegoKeytab"
label=${msg("SPNEGO keytab")} label=${msg("SPNEGO keytab")}
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
help=${msg( help=${msg(
"Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.", "Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
)} )}
></ak-secret-textarea-input> ></ak-private-textarea-input>
<ak-text-input <ak-text-input
name="spnegoCcache" name="spnegoCcache"
label=${msg("SPNEGO credentials cache")} label=${msg("SPNEGO credentials cache")}

View File

@ -2,8 +2,7 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
import { placeholderHelperText } from "@goauthentik/admin/helperText"; import { placeholderHelperText } from "@goauthentik/admin/helperText";
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-secret-text-input.js"; import "@goauthentik/components/ak-private-text-input.js";
import "@goauthentik/components/ak-slug-input.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -55,15 +54,14 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
<ak-slug-input <input
name="slug" type="text"
value=${ifDefined(this.instance?.slug)} value="${ifDefined(this.instance?.slug)}"
label=${msg("Slug")} class="pf-c-form-control"
required required
input-hint="code" />
></ak-slug-input> </ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled"> <ak-form-element-horizontal name="enabled">
<label class="pf-c-switch"> <label class="pf-c-switch">
<input <input
@ -262,11 +260,11 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
class="pf-c-form-control" class="pf-c-form-control"
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-text-input <ak-private-text-input
label=${msg("Bind Password")} label=${msg("Bind Password")}
name="bindPassword" name="bindPassword"
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
></ak-secret-text-input> ></ak-private-text-input>
<ak-form-element-horizontal label=${msg("Base DN")} required name="baseDn"> <ak-form-element-horizontal label=${msg("Base DN")} required name="baseDn">
<input <input
type="text" type="text"

View File

@ -8,9 +8,8 @@ import {
UserMatchingModeToLabel, UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils"; } 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 "@goauthentik/components/ak-private-textarea-input.js";
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-secret-textarea-input.js";
import "@goauthentik/components/ak-slug-input.js";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
@ -268,13 +267,16 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-slug-input <ak-form-element-horizontal label=${msg("Slug")} required name="slug">
name="slug" <input
value=${ifDefined(this.instance?.slug)} type="text"
label=${msg("Slug")} value="${ifDefined(this.instance?.slug)}"
required class="pf-c-form-control pf-m-monospace"
input-hint="code" autocomplete="off"
></ak-slug-input> spellcheck="false"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled"> <ak-form-element-horizontal name="enabled">
<label class="pf-c-switch"> <label class="pf-c-switch">
<input <input
@ -439,14 +441,14 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
/> />
<p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p> <p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-textarea-input <ak-private-textarea-input
label=${msg("Consumer secret")} label=${msg("Consumer secret")}
name="consumerSecret" name="consumerSecret"
input-hint="code" input-hint="code"
help=${msg("Also known as Client Secret.")} help=${msg("Also known as Client Secret.")}
required required
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
></ak-secret-textarea-input> ></ak-private-textarea-input>
<ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes"> <ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes">
<input <input
type="text" type="text"

View File

@ -10,7 +10,6 @@ import {
import { DEFAULT_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, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-slug-input.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js";
@ -129,7 +128,7 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
this.doAuth(); this.doAuth();
}} }}
> >
${msg("Re-authenticate with Plex")} ${msg("Re-authenticate with plex")}
</button> </button>
<ak-form-element-horizontal name="allowFriends"> <ak-form-element-horizontal name="allowFriends">
<label class="pf-c-switch"> <label class="pf-c-switch">
@ -184,15 +183,14 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
<ak-slug-input <input
name="slug" type="text"
value=${ifDefined(this.instance?.slug)} value="${ifDefined(this.instance?.slug)}"
label=${msg("Slug")} class="pf-c-form-control"
required required
input-hint="code" />
></ak-slug-input> </ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled"> <ak-form-element-horizontal name="enabled">
<label class="pf-c-switch"> <label class="pf-c-switch">
<input <input

View File

@ -9,7 +9,6 @@ import {
UserMatchingModeToLabel, UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils"; } 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 "@goauthentik/components/ak-slug-input.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -26,7 +25,7 @@ import {
DigestAlgorithmEnum, DigestAlgorithmEnum,
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum, GroupMatchingModeEnum,
SAMLNameIDPolicyEnum, NameIdPolicyEnum,
SAMLSource, SAMLSource,
SignatureAlgorithmEnum, SignatureAlgorithmEnum,
SourcesApi, SourcesApi,
@ -90,15 +89,14 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
<ak-slug-input <input
name="slug" type="text"
value=${ifDefined(this.instance?.slug)} value="${ifDefined(this.instance?.slug)}"
label=${msg("Slug")} class="pf-c-form-control"
required required
input-hint="code" />
></ak-slug-input> </ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled"> <ak-form-element-horizontal name="enabled">
<label class="pf-c-switch"> <label class="pf-c-switch">
<input <input
@ -353,37 +351,37 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
> >
<select class="pf-c-form-control"> <select class="pf-c-form-control">
<option <option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
?selected=${this.instance?.nameIdPolicy === ?selected=${this.instance?.nameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
> >
${msg("Persistent")} ${msg("Persistent")}
</option> </option>
<option <option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
?selected=${this.instance?.nameIdPolicy === ?selected=${this.instance?.nameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
> >
${msg("Email address")} ${msg("Email address")}
</option> </option>
<option <option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
?selected=${this.instance?.nameIdPolicy === ?selected=${this.instance?.nameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
> >
${msg("Windows")} ${msg("Windows")}
</option> </option>
<option <option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
?selected=${this.instance?.nameIdPolicy === ?selected=${this.instance?.nameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
> >
${msg("X509 Subject")} ${msg("X509 Subject")}
</option> </option>
<option <option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
?selected=${this.instance?.nameIdPolicy === ?selected=${this.instance?.nameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
> >
${msg("Transient")} ${msg("Transient")}
</option> </option>

View File

@ -1,7 +1,6 @@
import { placeholderHelperText } from "@goauthentik/admin/helperText"; import { placeholderHelperText } from "@goauthentik/admin/helperText";
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-slug-input.js";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -49,15 +48,14 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> {
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
<ak-slug-input <input
name="slug" type="text"
value=${ifDefined(this.instance?.slug)} value="${ifDefined(this.instance?.slug)}"
label=${msg("Slug")} class="pf-c-form-control"
required required
input-hint="code" />
></ak-slug-input> </ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled"> <ak-form-element-horizontal name="enabled">
<div class="pf-c-check"> <div class="pf-c-check">
<input <input

View File

@ -1,7 +1,7 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-secret-text-input.js"; import "@goauthentik/components/ak-private-text-input.js";
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";
@ -95,13 +95,13 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-text-input <ak-private-text-input
name="clientSecret" name="clientSecret"
label=${msg("Secret key")} label=${msg("Secret key")}
input-hint="code" input-hint="code"
required required
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
></ak-secret-text-input> ></ak-private-text-input>
</div> </div>
</ak-form-group> </ak-form-group>
<ak-form-group> <ak-form-group>
@ -125,12 +125,12 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
spellcheck="false" spellcheck="false"
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-text-input <ak-private-text-input
name="adminSecretKey" name="adminSecretKey"
label=${msg("Secret key")} label=${msg("Secret key")}
input-hint="code" input-hint="code"
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
></ak-secret-text-input> ></ak-private-text-input>
</div> </div>
</ak-form-group> </ak-form-group>
<ak-form-group expanded> <ak-form-group expanded>

View File

@ -1,7 +1,7 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-secret-text-input.js"; import "@goauthentik/components/ak-private-text-input.js";
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";
@ -77,11 +77,11 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-text-input <ak-private-text-input
name="password" name="password"
label=${msg("SMTP Password")} label=${msg("SMTP Password")}
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
></ak-secret-text-input> ></ak-private-text-input>
<ak-form-element-horizontal name="useTls"> <ak-form-element-horizontal name="useTls">
<label class="pf-c-switch"> <label class="pf-c-switch">

View File

@ -1,7 +1,7 @@
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-number-input"; import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-secret-text-input.js"; import "@goauthentik/components/ak-private-text-input.js";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -70,7 +70,7 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-text-input <ak-private-text-input
name="privateKey" name="privateKey"
label=${msg("Private Key")} label=${msg("Private Key")}
input-hint="code" input-hint="code"
@ -79,7 +79,7 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
help=${msg( help=${msg(
"Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.", "Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.",
)} )}
></ak-secret-text-input> ></ak-private-text-input>
<ak-switch-input <ak-switch-input
name="interactive" name="interactive"

View File

@ -1,6 +1,6 @@
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-secret-text-input.js"; import "@goauthentik/components/ak-private-text-input.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/utils/TimeDeltaHelp"; import "@goauthentik/elements/utils/TimeDeltaHelp";
@ -73,11 +73,11 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
class="pf-c-form-control" class="pf-c-form-control"
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-secret-text-input <ak-private-text-input
label=${msg("SMTP Password")} label=${msg("SMTP Password")}
name="password" name="password"
?revealed=${this.instance === undefined} ?revealed=${this.instance === undefined}
></ak-secret-text-input> ></ak-private-text-input>
<ak-form-element-horizontal name="useTls"> <ak-form-element-horizontal name="useTls">
<label class="pf-c-switch"> <label class="pf-c-switch">
<input <input

View File

@ -41,27 +41,14 @@ export class InvitationForm extends ModelForm<Invitation, string> {
} }
renderForm(): TemplateResult { renderForm(): TemplateResult {
const checkSlug = (ev: InputEvent) => { return html` <ak-form-element-horizontal slugMode label=${msg("Name")} required name="name">
if (ev && ev.target && ev.target instanceof HTMLInputElement) {
ev.target.value = (ev.target.value ?? "").replace(/[^a-z0-9-]/g, "");
}
};
return html` <ak-form-element-horizontal label=${msg("Name")} required name="name">
<input <input
type="text" type="text"
id="admin-stages-invitation-name"
value="${this.instance?.name || ""}" value="${this.instance?.name || ""}"
class="pf-c-form-control" class="pf-c-form-control"
required required
@input=${(ev: InputEvent) => checkSlug(ev)}
data-ak-slug="true" data-ak-slug="true"
/> />
<p class="pf-c-form__helper-text">
${msg(
"The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.",
)}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Expires")} required name="expires"> <ak-form-element-horizontal label=${msg("Expires")} required name="expires">
<input <input

View File

@ -1,6 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { dateTimeLocal } from "@goauthentik/common/temporal"; import { dateTimeLocal } from "@goauthentik/common/temporal";
import "@goauthentik/components/ak-hidden-text-input";
import { Form } from "@goauthentik/elements/forms/Form"; import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModalForm } from "@goauthentik/elements/forms/ModalForm"; import { ModalForm } from "@goauthentik/elements/forms/ModalForm";
@ -125,14 +124,19 @@ export class ServiceAccountForm extends Form<UserServiceAccountRequest> {
class="pf-c-form-control" class="pf-c-form-control"
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-hidden-text-input <ak-form-element-horizontal label=${msg("Password")}>
label=${msg("Password")} <input
value="${this.result?.token ?? ""}" type="text"
.help=${msg( readonly
"Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List.", value=${ifDefined(this.result?.token)}
)} class="pf-c-form-control"
> />
</ak-hidden-text-input> <p class="pf-c-form__helper-text">
${msg(
"Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List.",
)}
</p>
</ak-form-element-horizontal>
</form>`; </form>`;
} }

View File

@ -1,5 +1,21 @@
import * as base64js from "base64-js";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
export function b64enc(buf: Uint8Array): string {
return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
export function b64RawEnc(buf: Uint8Array): string {
return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_");
}
export function u8arr(input: string): Uint8Array {
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
c.charCodeAt(0),
);
}
export function checkWebAuthnSupport() { export function checkWebAuthnSupport() {
if ("credentials" in navigator) { if ("credentials" in navigator) {
return; return;
@ -9,3 +25,121 @@ export function checkWebAuthnSupport() {
} }
throw new Error(msg("WebAuthn not supported by browser.")); throw new Error(msg("WebAuthn not supported by browser."));
} }
/**
* Transforms items in the credentialCreateOptions generated on the server
* into byte arrays expected by the navigator.credentials.create() call
*/
export function transformCredentialCreateOptions(
credentialCreateOptions: PublicKeyCredentialCreationOptions,
userId: string,
): PublicKeyCredentialCreationOptions {
const user = credentialCreateOptions.user;
// Because json can't contain raw bytes, the server base64-encodes the User ID
// So to get the base64 encoded byte array, we first need to convert it to a regular
// string, then a byte array, re-encode it and wrap that in an array.
const stringId = decodeURIComponent(window.atob(userId));
user.id = u8arr(b64enc(u8arr(stringId)));
const challenge = u8arr(credentialCreateOptions.challenge.toString());
return {
...credentialCreateOptions,
challenge,
user,
};
}
export interface Assertion {
id: string;
rawId: string;
type: string;
registrationClientExtensions: string;
response: {
clientDataJSON: string;
attestationObject: string;
};
}
/**
* Transforms the binary data in the credential into base64 strings
* for posting to the server.
* @param {PublicKeyCredential} newAssertion
*/
export function transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion {
const attObj = new Uint8Array(
(newAssertion.response as AuthenticatorAttestationResponse).attestationObject,
);
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
const rawId = new Uint8Array(newAssertion.rawId);
const registrationClientExtensions = newAssertion.getClientExtensionResults();
return {
id: newAssertion.id,
rawId: b64enc(rawId),
type: newAssertion.type,
registrationClientExtensions: JSON.stringify(registrationClientExtensions),
response: {
clientDataJSON: b64enc(clientDataJSON),
attestationObject: b64enc(attObj),
},
};
}
export function transformCredentialRequestOptions(
credentialRequestOptions: PublicKeyCredentialRequestOptions,
): PublicKeyCredentialRequestOptions {
const challenge = u8arr(credentialRequestOptions.challenge.toString());
const allowCredentials = (credentialRequestOptions.allowCredentials || []).map(
(credentialDescriptor) => {
const id = u8arr(credentialDescriptor.id.toString());
return Object.assign({}, credentialDescriptor, { id });
},
);
return {
...credentialRequestOptions,
challenge,
allowCredentials,
};
}
export interface AuthAssertion {
id: string;
rawId: string;
type: string;
assertionClientExtensions: string;
response: {
clientDataJSON: string;
authenticatorData: string;
signature: string;
userHandle: string | null;
};
}
/**
* Encodes the binary data in the assertion into strings for posting to the server.
* @param {PublicKeyCredential} newAssertion
*/
export function transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion {
const response = newAssertion.response as AuthenticatorAssertionResponse;
const authData = new Uint8Array(response.authenticatorData);
const clientDataJSON = new Uint8Array(response.clientDataJSON);
const rawId = new Uint8Array(newAssertion.rawId);
const sig = new Uint8Array(response.signature);
const assertionClientExtensions = newAssertion.getClientExtensionResults();
return {
id: newAssertion.id,
rawId: b64enc(rawId),
type: newAssertion.type,
assertionClientExtensions: JSON.stringify(assertionClientExtensions),
response: {
clientDataJSON: b64RawEnc(clientDataJSON),
signature: b64RawEnc(sig),
authenticatorData: b64RawEnc(authData),
userHandle: null,
},
};
}

View File

@ -1,6 +1,5 @@
import { me } from "@goauthentik/common/users.js"; import { me } from "@goauthentik/common/users.js";
import { isUserRoute } from "@goauthentik/elements/router/utils.js"; import { isUserRoute } from "@goauthentik/elements/router/utils.js";
import { deepmerge, deepmergeInto } from "deepmerge-ts";
import { UiThemeEnum, UserSelf } from "@goauthentik/api"; import { UiThemeEnum, UserSelf } from "@goauthentik/api";
import { CurrentBrand } from "@goauthentik/api"; import { CurrentBrand } from "@goauthentik/api";
@ -97,12 +96,13 @@ export class DefaultUIConfig implements UIConfig {
let globalUiConfig: Promise<UIConfig>; let globalUiConfig: Promise<UIConfig>;
export function getConfigForUser(user: UserSelf): UIConfig { export function getConfigForUser(user: UserSelf): UIConfig {
const settings = user.settings as UIConfig; const settings = user.settings;
const config = new DefaultUIConfig(); let config = new DefaultUIConfig();
if (!settings) { if (!settings) {
return config; return config;
} }
return deepmerge({ ...config }, settings); config = Object.assign(new DefaultUIConfig(), settings);
return config;
} }
export function uiConfig(): Promise<UIConfig> { export function uiConfig(): Promise<UIConfig> {

View File

@ -1,4 +1,4 @@
import { AKElement, type AKElementProps } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/HorizontalFormElement.js"; import "@goauthentik/elements/forms/HorizontalFormElement.js";
import { TemplateResult, html, nothing } from "lit"; import { TemplateResult, html, nothing } from "lit";
@ -6,19 +6,6 @@ import { property } from "lit/decorators.js";
type HelpType = TemplateResult | typeof nothing; type HelpType = TemplateResult | typeof nothing;
export interface HorizontalLightComponentProps<T> extends AKElementProps {
name: string;
label?: string;
required?: boolean;
help?: string;
bighelp?: TemplateResult | TemplateResult[];
hidden?: boolean;
invalid?: boolean;
errorMessages?: string[];
value?: T;
inputHint?: string;
}
export class HorizontalLightComponent<T> extends AKElement { export class HorizontalLightComponent<T> extends AKElement {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to // we're not actually using that and, for the meantime, we need the form handlers to be able to
@ -31,81 +18,37 @@ export class HorizontalLightComponent<T> extends AKElement {
return this; return this;
} }
/**
* The name attribute for the form element
* @property
* @attribute
*/
@property({ type: String, reflect: true }) @property({ type: String, reflect: true })
name!: string; name!: string;
/**
* The label for the input control
* @property
* @attribute
*/
@property({ type: String, reflect: true }) @property({ type: String, reflect: true })
label = ""; label = "";
/**
* @property
* @attribute
*/
@property({ type: Boolean, reflect: true }) @property({ type: Boolean, reflect: true })
required = false; required = false;
/**
* Help text to display below the form element. Optional
* @property
* @attribute
*/
@property({ type: String, reflect: true }) @property({ type: String, reflect: true })
help = ""; help = "";
/**
* Extended help content. Optional. Expects to be a TemplateResult
* @property
*/
@property({ type: Object }) @property({ type: Object })
bighelp?: TemplateResult | TemplateResult[]; bighelp?: TemplateResult | TemplateResult[];
/**
* @property
* @attribute
*/
@property({ type: Boolean, reflect: true }) @property({ type: Boolean, reflect: true })
hidden = false; hidden = false;
/**
* @property
* @attribute
*/
@property({ type: Boolean, reflect: true }) @property({ type: Boolean, reflect: true })
invalid = false; invalid = false;
/**
* @property
*/
@property({ attribute: false }) @property({ attribute: false })
errorMessages: string[] = []; errorMessages: string[] = [];
/**
* @attribute
* @property
*/
@property({ attribute: false }) @property({ attribute: false })
value?: T; value?: T;
/**
* Input hint.
* - `code`: uses a monospace font and disables spellcheck & autocomplete
* @property
* @attribute
*/
@property({ type: String, attribute: "input-hint" }) @property({ type: String, attribute: "input-hint" })
inputHint = ""; inputHint = "";
protected renderControl() { renderControl() {
throw new Error("Must be implemented in a subclass"); throw new Error("Must be implemented in a subclass");
} }

View File

@ -1,159 +0,0 @@
import { bound } from "#elements/decorators/bound";
import { msg } from "@lit/localize";
import { css, html } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
HorizontalLightComponent,
HorizontalLightComponentProps,
} from "./HorizontalLightComponent";
import "./ak-visibility-toggle.js";
import type { VisibilityToggleProps } from "./ak-visibility-toggle.js";
type BaseProps = HorizontalLightComponentProps<string> &
Pick<VisibilityToggleProps, "showMessage" | "hideMessage">;
export interface AkHiddenTextInputProps extends BaseProps {
revealed: boolean;
placeholder?: string;
}
export type InputLike = HTMLTextAreaElement | HTMLInputElement;
/**
* @element ak-hidden-text-input
* @class AkHiddenTextInput
*
* A text-input field with a visibility control, so you can show/hide sensitive fields.
*
* ## CSS Parts
* @csspart container - The main container div
* @csspart input - The input element
* @csspart toggle - The visibility toggle button
*
*/
@customElement("ak-hidden-text-input")
export class AkHiddenTextInput<T extends InputLike = HTMLInputElement>
extends HorizontalLightComponent<string>
implements AkHiddenTextInputProps
{
public static get styles() {
return [
css`
main {
display: flex;
}
`,
];
}
/**
* @property
* @attribute
*/
@property({ type: String, reflect: true })
public value = "";
/**
* @property
* @attribute
*/
@property({ type: Boolean, reflect: true })
public revealed = false;
/**
* Text for when the input has no set value
*
* @property
* @attribute
*/
@property({ type: String })
public placeholder?: string;
/**
* Specify kind of help the browser should try to provide
*
* @property
* @attribute
*/
@property({ type: String })
public autocomplete?: "none" | AutoFill;
/**
* @property
* @attribute
*/
@property({ type: String, attribute: "show-message" })
public showMessage = msg("Show field content");
/**
* @property
* @attribute
*/
@property({ type: String, attribute: "hide-message" })
public hideMessage = msg("Hide field content");
@query("#main > input")
protected inputField!: T;
@bound
private handleToggleVisibility() {
this.revealed = !this.revealed;
// Maintain focus on input after toggle
this.updateComplete.then(() => {
if (this.inputField && document.activeElement === this) {
this.inputField.focus();
}
});
}
// TODO: Because of the peculiarities of how HorizontalLightComponent works, keeping its content
// in the LightDom so the inner components actually inherit styling, the normal `css` options
// aren't available. Embedding styles is bad styling, and we'll fix it in the next style
// refresh.
protected renderInputField(setValue: (ev: InputEvent) => void, code: boolean) {
return html` <input
style="flex: 1 1 auto; min-width: 0;"
part="input"
type=${this.revealed ? "text" : "password"}
@input=${setValue}
value=${ifDefined(this.value)}
placeholder=${ifDefined(this.placeholder)}
class="${classMap({
"pf-c-form-control": true,
"pf-m-monospace": code,
})}"
spellcheck=${code ? "false" : "true"}
?required=${this.required}
/>`;
}
protected override renderControl() {
const code = this.inputHint === "code";
const setValue = (ev: InputEvent) => {
this.value = (ev.target as T).value;
};
return html` <div style="display: flex; gap: 0.25rem">
${this.renderInputField(setValue, code)}
<!-- -->
<ak-visibility-toggle
part="toggle"
style="flex: 0 0 auto; align-self: flex-start"
?open=${this.revealed}
show-message=${this.showMessage}
hide-message=${this.hideMessage}
@click=${() => (this.revealed = !this.revealed)}
></ak-visibility-toggle>
</div>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-hidden-text-input": AkHiddenTextInput;
}
}

View File

@ -1,128 +0,0 @@
import { css, html } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { AkHiddenTextInput, type AkHiddenTextInputProps } from "./ak-hidden-text-input.js";
export interface AkHiddenTextAreaInputProps extends AkHiddenTextInputProps {
/**
* Number of visible text lines (rows)
*/
rows?: number;
/**
* Number of visible character width (cols)
*/
cols?: number;
/**
* How the textarea can be resized
*/
resize?: "none" | "both" | "horizontal" | "vertical";
/**
* Whether text should wrap
*/
wrap?: "soft" | "hard" | "off";
}
/**
* @element ak-hidden-text-input
* @class AkHiddenTextInput
*
* A text-input field with a visibility control, so you can show/hide sensitive fields.
*
* ## CSS Parts
* @csspart container - The main container div
* @csspart input - The input element
* @csspart toggle - The visibility toggle button
*
*/
@customElement("ak-hidden-textarea-input")
export class AkHiddenTextAreaInput
extends AkHiddenTextInput<HTMLTextAreaElement>
implements AkHiddenTextAreaInputProps
{
/* These are mostly just forwarded to the textarea component. */
/**
* @property
* @attribute
*/
@property({ type: Number })
rows?: number = 4;
/**
* @property
* @attribute
*/
@property({ type: Number })
cols?: number;
/**
* @property
* @attribute
*
* You want `resize=true` so that the resize value is visible in the component tag, activating
* the CSS associated with these values.
*/
@property({ type: String, reflect: true })
resize?: "none" | "both" | "horizontal" | "vertical" = "vertical";
/**
* @property
* @attribute
*/
@property({ type: String })
wrap?: "soft" | "hard" | "off" = "soft";
@query("#main > textarea")
protected inputField!: HTMLTextAreaElement;
get displayValue() {
const value = this.value ?? "";
if (this.revealed) {
return value;
}
return value
.split("\n")
.reduce((acc: string[], line: string) => [...acc, "*".repeat(line.length)], [])
.join("\n");
}
// TODO: Because of the peculiarities of how HorizontalLightComponent works, keeping its content
// in the LightDom so the inner components actually inherit styling, the normal `css` options
// aren't available. Embedding styles is bad styling, and we'll fix it in the next style
// refresh.
protected override renderInputField(setValue: (ev: InputEvent) => void, code: boolean) {
const wrap = this.revealed ? this.wrap : "soft";
return html`
<textarea
style="flex: 1 1 auto; min-width: 0;"
part="textarea"
@input=${setValue}
placeholder=${ifDefined(this.placeholder)}
rows=${ifDefined(this.rows)}
cols=${ifDefined(this.cols)}
wrap=${ifDefined(wrap)}
class=${classMap({
"pf-c-form-control": true,
"pf-m-monospace": code,
})}
spellcheck=${code ? "false" : "true"}
?required=${this.required}
>
${this.displayValue}</textarea
>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-hidden-textarea-input": AkHiddenTextAreaInput;
}
}

View File

@ -8,8 +8,8 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { HorizontalLightComponent } from "./HorizontalLightComponent"; import { HorizontalLightComponent } from "./HorizontalLightComponent";
@customElement("ak-secret-text-input") @customElement("ak-private-text-input")
export class AkSecretTextInput extends HorizontalLightComponent<string> { export class AkPrivateTextInput extends HorizontalLightComponent<string> {
@property({ type: String, reflect: true }) @property({ type: String, reflect: true })
public value = ""; public value = "";
@ -23,7 +23,7 @@ export class AkSecretTextInput extends HorizontalLightComponent<string> {
this.revealed = true; this.revealed = true;
} }
#renderSecretInput() { #renderPrivateInput() {
return html`<div class="pf-c-form__horizontal-group" @click=${() => this.#onReveal()}> return html`<div class="pf-c-form__horizontal-group" @click=${() => this.#onReveal()}>
<input <input
class="pf-c-form-control" class="pf-c-form-control"
@ -60,14 +60,14 @@ export class AkSecretTextInput extends HorizontalLightComponent<string> {
} }
public override renderControl() { public override renderControl() {
return this.revealed ? this.renderVisibleInput() : this.#renderSecretInput(); return this.revealed ? this.renderVisibleInput() : this.#renderPrivateInput();
} }
} }
export default AkSecretTextInput; export default AkPrivateTextInput;
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"ak-secret-text-input": AkSecretTextInput; "ak-private-text-input": AkPrivateTextInput;
} }
} }

View File

@ -5,10 +5,10 @@ import { customElement, property } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js"; import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { AkSecretTextInput } from "./ak-secret-text-input.js"; import { AkPrivateTextInput } from "./ak-private-text-input.js";
@customElement("ak-secret-textarea-input") @customElement("ak-private-textarea-input")
export class AkSecretTextAreaInput extends AkSecretTextInput { export class AkPrivateTextAreaInput extends AkPrivateTextInput {
protected override renderVisibleInput() { protected override renderVisibleInput() {
const code = this.inputHint === "code"; const code = this.inputHint === "code";
const setValue = (ev: InputEvent) => { const setValue = (ev: InputEvent) => {
@ -34,10 +34,10 @@ export class AkSecretTextAreaInput extends AkSecretTextInput {
} }
} }
export default AkSecretTextAreaInput; export default AkPrivateTextAreaInput;
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"ak-secret-textarea-input": AkSecretTextAreaInput; "ak-private-textarea-input": AkPrivateTextAreaInput;
} }
} }

View File

@ -1,5 +1,4 @@
import { bound } from "@goauthentik/elements/decorators/bound.js"; import { formatSlug } from "@goauthentik/elements/router/utils.js";
import { kebabCase } from "change-case";
import { html } from "lit"; import { html } from "lit";
import { customElement, property, query } from "lit/decorators.js"; import { customElement, property, query } from "lit/decorators.js";
@ -7,83 +6,59 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { HorizontalLightComponent } from "./HorizontalLightComponent"; import { HorizontalLightComponent } from "./HorizontalLightComponent";
const slugify = (s: string) => kebabCase(s, { suffixCharacters: "-" });
/**
* @element ak-slug-input
* @class AkSlugInput
*
* A wrapper around `ak-form-element-horizontal` and a text input control that listens for input on
* a peer text input control and automatically mirrors that control's value, transforming the value
* into a slug and displaying it separately.
*
* If the user manually changes the slug, mirroring and transformation stop. If, after that, both
* fields are cleared manually, mirroring and transformation resume.
*
* ## Limitations:
*
* Both the source text field and the slug field must be rendered in the same render pass (i.e.,
* part of the same singular call to a `render` function) so that the slug field can find its
* source.
*
* For the same reason, both the source text field and the slug field must share the same immediate
* parent DOM object.
*
* Since we expect the source text field and the slug to be part of the same form and rendered not
* just in the same form but in the same form group, these are not considered burdensome
* restrictions.
*/
@customElement("ak-slug-input") @customElement("ak-slug-input")
export class AkSlugInput extends HorizontalLightComponent<string> { export class AkSlugInput extends HorizontalLightComponent<string> {
/**
* A selector indicating the source text input control. Must be unique within the whole DOM
* context of the slug and source controls. The most common use in authentik is the default:
* slugifying the "name" of something.
*/
@property({ type: String })
public source = "[name='name']";
@property({ type: String, reflect: true }) @property({ type: String, reflect: true })
public value = ""; value = "";
@property({ type: String })
source = "";
origin?: HTMLInputElement | null;
@query("input") @query("input")
private input!: HTMLInputElement; input!: HTMLInputElement;
#origin?: HTMLInputElement | null; touched: boolean = false;
#touched: boolean = false; constructor() {
super();
this.slugify = this.slugify.bind(this);
this.handleTouch = this.handleTouch.bind(this);
}
firstUpdated() {
this.input.addEventListener("input", this.handleTouch);
}
// Do not stop propagation of this event; it must be sent up the tree so that a parent // Do not stop propagation of this event; it must be sent up the tree so that a parent
// component, such as a custom forms manager, may receive it. // component, such as a custom forms manager, may receive it.
protected handleTouch(ev: Event) { handleTouch(ev: Event) {
this.value = this.input.value = slugify(this.input.value); this.input.value = formatSlug(this.input.value);
this.value = this.input.value;
// Reset 'touched' status if the slug & target have been reset if (this.origin && this.origin.value === "" && this.input.value === "") {
if (this.#origin && this.#origin.value === "" && this.input.value === "") { this.touched = false;
this.#touched = false;
return; return;
} }
if (ev && ev.target && ev.target instanceof HTMLInputElement) { if (ev && ev.target && ev.target instanceof HTMLInputElement) {
this.#touched = true; this.touched = true;
} }
} }
@bound slugify(ev: Event) {
protected slugify(ev: Event) {
if (!(ev && ev.target && ev.target instanceof HTMLInputElement)) { if (!(ev && ev.target && ev.target instanceof HTMLInputElement)) {
return; return;
} }
// Reset 'touched' status if the slug & target have been reset // Reset 'touched' status if the slug & target have been reset
if (ev.target.value === "" && this.input.value === "") { if (ev.target.value === "" && this.input.value === "") {
this.#touched = false; this.touched = false;
} }
// Don't proceed if the user has hand-modified the slug. (Note the order of statements: if // Don't proceed if the user has hand-modified the slug
// the user hand modified the slug to be empty as part of resetting the slug/source if (this.touched) {
// relationship, that's a "not-touched" condition and falls through.)
if (this.#touched) {
return; return;
} }
@ -92,7 +67,7 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
// "any event which adds or removes a character but leaves the rest of the slug looking like // "any event which adds or removes a character but leaves the rest of the slug looking like
// the previous iteration, set it to the current iteration." // the previous iteration, set it to the current iteration."
const newSlug = slugify(ev.target.value); const newSlug = formatSlug(ev.target.value);
const oldSlug = this.input.value; const oldSlug = this.input.value;
const [shorter, longer] = const [shorter, longer] =
newSlug.length < oldSlug.length ? [newSlug, oldSlug] : [oldSlug, newSlug]; newSlug.length < oldSlug.length ? [newSlug, oldSlug] : [oldSlug, newSlug];
@ -106,6 +81,7 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
// to listeners, both the name and value of the host must match those of the target // to listeners, both the name and value of the host must match those of the target
// input. The name is already handled since it's both required and automatically // input. The name is already handled since it's both required and automatically
// forwarded to our templated input, but the value must also be set. // forwarded to our templated input, but the value must also be set.
this.value = this.input.value = newSlug; this.value = this.input.value = newSlug;
this.dispatchEvent( this.dispatchEvent(
new Event("input", { new Event("input", {
@ -115,36 +91,38 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
); );
} }
public override disconnectedCallback() { connectedCallback() {
if (this.#origin) { super.connectedCallback();
this.#origin.removeEventListener("input", this.slugify);
// Set up listener on source element, so we can slugify the content.
setTimeout(() => {
if (this.source) {
const rootNode = this.getRootNode();
if (rootNode instanceof ShadowRoot || rootNode instanceof Document) {
this.origin = rootNode.querySelector(this.source);
}
if (this.origin) {
this.origin.addEventListener("input", this.slugify);
}
}
}, 0);
}
disconnectedCallback() {
if (this.origin) {
this.origin.removeEventListener("input", this.slugify);
} }
super.disconnectedCallback(); super.disconnectedCallback();
} }
public override renderControl() { renderControl() {
return html`<input return html`<input
@input=${(ev: Event) => this.handleTouch(ev)}
type="text" type="text"
value=${ifDefined(this.value)} value=${ifDefined(this.value)}
class="pf-c-form-control" class="pf-c-form-control"
?required=${this.required} ?required=${this.required}
/>`; />`;
} }
public override firstUpdated() {
if (!this.source) {
return;
}
const rootNode = this.getRootNode();
if (rootNode instanceof ShadowRoot || rootNode instanceof Document) {
this.#origin = rootNode.querySelector(this.source);
}
if (this.#origin) {
this.#origin.addEventListener("input", this.slugify);
}
}
} }
export default AkSlugInput; export default AkSlugInput;

View File

@ -1,89 +0,0 @@
import { AKElement } from "@goauthentik/elements/Base.js";
import { msg } from "@lit/localize";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
export interface VisibilityToggleProps {
open: boolean;
disabled: boolean;
showMessage: string;
hideMessage: string;
}
/**
* @component ak-visibility-toggle
* @class VisibilityToggle
*
* A straightforward two-state iconic button we use in a few places as way of telling users to hide
* or show something secret, such as a password or private key. Expects the client to manage its
* state.
*
* @events
* - click: when the toggle is clicked.
*/
@customElement("ak-visibility-toggle")
export class VisibilityToggle extends AKElement implements VisibilityToggleProps {
static get styles() {
return [PFBase, PFButton];
}
/**
* @property
* @attribute
*/
@property({ type: Boolean, reflect: true })
open = false;
/**
* @property
* @attribute
*/
@property({ type: Boolean, reflect: true })
disabled = false;
/**
* @property
* @attribute
*/
@property({ type: String, attribute: "show-message" })
showMessage = msg("Show field content");
/**
* @property
* @attribute
*/
@property({ type: String, attribute: "hide-message" })
hideMessage = msg("Hide field content");
render() {
const [label, icon] = this.open
? [this.hideMessage, "fa-eye"]
: [this.showMessage, "fa-eye-slash"];
const onClick = (ev: PointerEvent) => {
ev.stopPropagation();
this.dispatchEvent(new PointerEvent(ev.type, ev));
};
return html`<button
aria-label=${label}
title=${label}
@click=${onClick}
?disabled=${this.disabled}
class="pf-c-button pf-m-control"
type="button"
>
<i class="fas ${icon}" aria-hidden="true"></i>
</button>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-visibility-toggle": VisibilityToggle;
}
}

View File

@ -94,8 +94,7 @@ export class ObjectChangelog extends Table<Event> {
renderEmpty(): TemplateResult { renderEmpty(): TemplateResult {
return super.renderEmpty( return super.renderEmpty(
html`<ak-empty-state html`<ak-empty-state header=${msg("No Events found.")}>
><span slot="header">${msg("No Events found.")}</span>
<div slot="body">${msg("No matching events could be found.")}</div> <div slot="body">${msg("No matching events could be found.")}</div>
</ak-empty-state>`, </ak-empty-state>`,
); );

View File

@ -66,8 +66,7 @@ export class UserEvents extends Table<Event> {
renderEmpty(): TemplateResult { renderEmpty(): TemplateResult {
return super.renderEmpty( return super.renderEmpty(
html`<ak-empty-state html`<ak-empty-state header=${msg("No Events found.")}>
><span slot="header">${msg("No Events found.")}</span>
<div slot="body">${msg("No matching events could be found.")}</div> <div slot="body">${msg("No matching events could be found.")}</div>
</ak-empty-state>`, </ak-empty-state>`,
); );

View File

@ -1,93 +0,0 @@
import type { Meta, StoryObj } from "@storybook/web-components";
import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import "../ak-hidden-text-input";
import { type AkHiddenTextInput, type AkHiddenTextInputProps } from "../ak-hidden-text-input.js";
const metadata: Meta<AkHiddenTextInputProps> = {
title: "Components / <ak-hidden-text-input>",
component: "ak-hidden-text-input",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: `
# Hidden Text Input Component
A text-input field with a visibility control, so you can show/hide sensitive fields.
`,
},
},
layout: "padded",
},
argTypes: {
label: {
control: "text",
description: "Label text for the input field",
},
value: {
control: "text",
description: "Current value of the input",
},
revealed: {
control: "boolean",
description: "Whether the text is currently visible",
},
placeholder: {
control: "text",
description: "Placeholder text for the input",
},
required: {
control: "boolean",
description: "Whether the input is required",
},
inputHint: {
control: "select",
options: ["text", "code"],
description: "Input type hint for styling and behavior",
},
showMessage: {
control: "text",
description: "Custom message for show action",
},
hideMessage: {
control: "text",
description: "Custom message for hide action",
},
},
};
export default metadata;
type Story = StoryObj<AkHiddenTextInput>;
const Template: Story = {
args: {
label: "Hidden Text Input",
value: "",
revealed: false,
},
render: (args) => html`
<ak-hidden-text-input
label=${ifDefined(args.label)}
value=${ifDefined(args.value)}
?revealed=${args.revealed}
placeholder=${ifDefined(args.placeholder)}
?required=${args.required}
input-hint=${ifDefined(args.inputHint)}
show-message=${ifDefined(args.showMessage)}
hide-message=${ifDefined(args.hideMessage)}
></ak-hidden-text-input>
`,
};
export const Password: Story = {
...Template,
args: {
label: "Password",
placeholder: "Enter your password",
required: true,
},
};

View File

@ -1,140 +0,0 @@
import type { Meta, StoryObj } from "@storybook/web-components";
import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import "../ak-hidden-textarea-input";
import {
type AkHiddenTextAreaInput,
type AkHiddenTextAreaInputProps,
} from "../ak-hidden-textarea-input.js";
const metadata: Meta<AkHiddenTextAreaInputProps> = {
title: "Components / <ak-hidden-textarea-input>",
component: "ak-hidden-textarea-input",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: `
# Hidden Textarea Input Component
A textarea input field with a visibility control, so you can show/hide sensitive fields.
`,
},
},
layout: "padded",
},
argTypes: {
label: {
control: "text",
description: "Label text for the input field",
},
value: {
control: "text",
description: "Current value of the input",
},
revealed: {
control: "boolean",
description: "Whether the text is currently visible",
},
placeholder: {
control: "text",
description: "Placeholder text for the input",
},
required: {
control: "boolean",
description: "Whether the input is required",
},
inputHint: {
control: "select",
options: ["text", "code"],
description: "Input type hint for styling and behavior",
},
showMessage: {
control: "text",
description: "Custom message for show action",
},
hideMessage: {
control: "text",
description: "Custom message for hide action",
},
rows: {
control: { type: "number", min: 1, max: 50 },
description: "Number of visible text lines",
},
cols: {
control: { type: "number", min: 10, max: 200 },
description: "Number of visible character width",
},
resize: {
control: "select",
options: ["none", "both", "horizontal", "vertical"],
description: "How the textarea can be resized",
},
wrap: {
control: "select",
options: ["soft", "hard", "off"],
description: "Text wrapping behavior",
},
},
};
export default metadata;
type Story = StoryObj<AkHiddenTextAreaInput>;
const Template: Story = {
args: {
label: "Hidden Textarea Input",
value: "",
revealed: false,
rows: 4,
},
render: (args) => html`
<ak-hidden-textarea-input
label=${ifDefined(args.label)}
value=${ifDefined(args.value)}
?revealed=${args.revealed}
placeholder=${ifDefined(args.placeholder)}
rows=${ifDefined(args.rows)}
cols=${ifDefined(args.cols)}
resize=${ifDefined(args.resize)}
wrap=${ifDefined(args.wrap)}
?required=${args.required}
input-hint=${ifDefined(args.inputHint)}
show-message=${ifDefined(args.showMessage)}
hide-message=${ifDefined(args.hideMessage)}
></ak-hidden-textarea-input>
`,
};
export const SslCertificate: Story = {
...Template,
args: {
label: "SSL Certificate",
value: `-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKoK/heBjcOuMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTcwNTEwMTk0MDA2WhcNMTgwNTEwMTk0MDA2WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE3MDUxMDE5NDAwNloXDTE4MDUxMDE5
NDAwNlowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNV
BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBALdUlNS31SzxwoFShahGfjHj6GgpcVbzL1Siq0Pqnf82T6M2
EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggE
BAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqn
f82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgM
BAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeM
Hyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDu
neMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJ
kPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAE=
-----END CERTIFICATE-----`,
inputHint: "code",
rows: 15,
resize: "vertical",
showMessage: "Show certificate content",
hideMessage: "Hide certificate content",
autocomplete: "off",
},
};

View File

@ -1,121 +0,0 @@
import type { Meta, StoryObj } from "@storybook/web-components";
import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import "../ak-visibility-toggle";
import { type VisibilityToggle, type VisibilityToggleProps } from "../ak-visibility-toggle.js";
const metadata: Meta<VisibilityToggleProps> = {
title: "Elements/<ak-visibility-toggle>",
component: "ak-visibility-toggle",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: `
# Visibility Toggle Component
A straightforward two-state iconic button for toggling the visibility of sensitive content such as passwords, private keys, or other secret information.
- Use for sensitive content that users might want to temporarily reveal
- There are default hide/show messages for screen readers, but they can be overridden
- Clients always handle the state
- The \`open\` state is false by default; we assume you want sensitive content hidden at start
`,
},
},
layout: "padded",
},
argTypes: {
open: {
control: "boolean",
description: "Whether the toggle is in the 'show' state (true) or 'hide' state (false)",
},
showMessage: {
control: "text",
description:
'Message for screen readers when in hide state (default: "Show field content")',
},
hideMessage: {
control: "text",
description:
'Message for screen readers when in show state (default: "Hide field content")',
},
disabled: {
control: "boolean",
description: "Whether the button should be disabled (for demo purposes)",
},
},
};
export default metadata;
type Story = StoryObj<VisibilityToggle>;
const Template: Story = {
args: {
open: false,
showMessage: "Show field content",
hideMessage: "Hide field content",
},
render: (args) => html`
<ak-visibility-toggle
?open=${args.open}
show-message=${ifDefined(args.showMessage)}
hide-message=${ifDefined(args.hideMessage)}
@click=${(e: Event) => {
const target = e.target as VisibilityToggle;
target.open = !target.open;
// In a real application, you would also toggle the visibility
// of the associated content here
}}
></ak-visibility-toggle>
`,
};
// Password field integration example
export const PasswordFieldExample: Story = {
args: {
showMessage: "Reveal password",
hideMessage: "Conceal password",
},
render: () => {
let isVisible = false;
const toggleVisibility = (e: Event) => {
isVisible = !isVisible;
const toggle = e.target as VisibilityToggle;
const passwordField = document.querySelector("#demo-password") as HTMLInputElement;
toggle.open = isVisible;
if (passwordField) {
passwordField.type = isVisible ? "text" : "password";
}
};
return html`
<div style="display: flex; flex-direction: column; gap: 1rem; max-width: 300px;">
<label for="demo-password" style="font-weight: bold;">Password:</label>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<input
id="demo-password"
type="password"
value="supersecretpassword123"
style="flex: 1; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px;"
readonly
/>
<ak-visibility-toggle
?open=${isVisible}
show-message="Show password"
hide-message="Hide password"
@click=${toggleVisibility}
></ak-visibility-toggle>
</div>
<p style="font-size: 0.875rem; color: #666;">
Click the eye icon to toggle password visibility
</p>
</div>
`;
},
};

View File

@ -16,12 +16,8 @@ import { property } from "lit/decorators.js";
import { UiThemeEnum } from "@goauthentik/api"; import { UiThemeEnum } from "@goauthentik/api";
export interface AKElementProps {
activeTheme: ResolvedUITheme;
}
@localized() @localized()
export class AKElement extends LitElement implements AKElementProps { export class AKElement extends LitElement {
//#region Static Properties //#region Static Properties
public static styles?: Array<CSSResult | CSSModule>; public static styles?: Array<CSSResult | CSSModule>;

View File

@ -3,7 +3,6 @@ import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Spinner"; import "@goauthentik/elements/Spinner";
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types"; import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
import { spread } from "@open-wc/lit-helpers"; import { spread } from "@open-wc/lit-helpers";
import { SlotController } from "@patternfly/pfe-core/controllers/slot-controller.js";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { css, html, nothing } from "lit"; import { css, html, nothing } from "lit";
@ -34,8 +33,6 @@ export class EmptyState extends AKElement implements IEmptyState {
@property() @property()
header?: string; header?: string;
slots = new SlotController(this, "header", "body", "primary");
static get styles() { static get styles() {
return [ return [
PFBase, PFBase,
@ -51,12 +48,6 @@ export class EmptyState extends AKElement implements IEmptyState {
} }
render() { render() {
const showHeader = this.loading || this.slots.hasSlotted("header");
const header = () =>
this.slots.hasSlotted("header")
? html`<slot name="header"></slot>`
: html`<span>${msg("Loading")}</span>`;
return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}"> return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
${this.loading ${this.loading
@ -68,17 +59,15 @@ export class EmptyState extends AKElement implements IEmptyState {
"fa-question-circle"} pf-c-empty-state__icon" "fa-question-circle"} pf-c-empty-state__icon"
aria-hidden="true" aria-hidden="true"
></i>`} ></i>`}
${showHeader ? html` <h1 class="pf-c-title pf-m-lg">${header()}</h1>` : nothing} <h1 class="pf-c-title pf-m-lg">
${this.slots.hasSlotted("body") ${this.loading && this.header === undefined ? msg("Loading") : this.header}
? html` <div class="pf-c-empty-state__body"> </h1>
<slot name="body"></slot> <div class="pf-c-empty-state__body">
</div>` <slot name="body"></slot>
: nothing} </div>
${this.slots.hasSlotted("primary") <div class="pf-c-empty-state__primary">
? html` <div class="pf-c-empty-state__primary"> <slot name="primary"></slot>
<slot name="primary"></slot> </div>
</div>`
: nothing}
</div> </div>
</div>`; </div>`;
} }

View File

@ -33,7 +33,7 @@ import {
function localeComparator(a: DualSelectPair, b: DualSelectPair) { function localeComparator(a: DualSelectPair, b: DualSelectPair) {
const aSortBy = a[2] || a[0]; const aSortBy = a[2] || a[0];
const bSortBy = b[2] || b[0]; const bSortBy = b[2] || a[0];
return aSortBy.localeCompare(bSortBy); return aSortBy.localeCompare(bSortBy);
} }

View File

@ -200,8 +200,7 @@ export abstract class AKChart<T> extends AKElement {
<div class="container"> <div class="container">
${this.error ${this.error
? html` ? html`
<ak-empty-state icon="fa-times" <ak-empty-state header="${msg("Failed to fetch data.")}" icon="fa-times">
><span slot="header">${msg("Failed to fetch data.")}</span>
<p slot="body">${pluckErrorDetail(this.error)}</p> <p slot="body">${pluckErrorDetail(this.error)}</p>
</ak-empty-state> </ak-empty-state>
` `

View File

@ -40,9 +40,7 @@ export class LogViewer extends Table<LogEvent> {
renderEmpty(): TemplateResult { renderEmpty(): TemplateResult {
return super.renderEmpty( return super.renderEmpty(
html`<ak-empty-state html`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`,
><span slot="header">${msg("No log messages.")}</span>
</ak-empty-state>`,
); );
} }

View File

@ -7,6 +7,7 @@ import { AKElement } from "@goauthentik/elements/Base";
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers"; import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { formatSlug } from "@goauthentik/elements/router/utils.js";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
@ -196,6 +197,39 @@ export abstract class Form<T> extends AKElement {
return this.successMessage; return this.successMessage;
} }
/**
* After rendering the form, if there is both a `name` and `slug` element within the form,
* events the `name` element so that the slug will always have a slugified version of the
* `name.`. This duplicates functionality within ak-form-element-horizontal.
*/
updated(): void {
this.shadowRoot
?.querySelectorAll("ak-form-element-horizontal[name=name]")
.forEach((nameInput) => {
const input = nameInput.firstElementChild as HTMLInputElement;
const form = nameInput.closest("form");
if (form === null) {
return;
}
const slugFieldWrapper = form.querySelector(
"ak-form-element-horizontal[name=slug]",
);
if (!slugFieldWrapper) {
return;
}
const slugField = slugFieldWrapper.firstElementChild as HTMLInputElement;
// Only attach handler if the slug is already equal to the name
// if not, they are probably completely different and shouldn't update
// each other
if (formatSlug(input.value) !== slugField.value) {
return;
}
nameInput.addEventListener("input", () => {
slugField.value = formatSlug(input.value);
});
});
}
resetForm(): void { resetForm(): void {
const form = this.shadowRoot?.querySelector<HTMLFormElement>("form"); const form = this.shadowRoot?.querySelector<HTMLFormElement>("form");
form?.reset(); form?.reset();

View File

@ -77,6 +77,9 @@ export class HorizontalFormElement extends AKElement {
@property({ attribute: false }) @property({ attribute: false })
errorMessages: string[] | string[][] = []; errorMessages: string[] | string[][] = [];
@property({ type: Boolean })
slugMode = false;
_invalid = false; _invalid = false;
/* If this property changes, we want to make sure the parent control is "opened" so /* If this property changes, we want to make sure the parent control is "opened" so
@ -106,6 +109,13 @@ export class HorizontalFormElement extends AKElement {
this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => {
input.focus(); input.focus();
}); });
if (this.name === "slug" || this.slugMode) {
this.querySelectorAll<HTMLInputElement>("input[type='text']").forEach((input) => {
input.addEventListener("keyup", () => {
input.value = formatSlug(input.value);
});
});
}
this.querySelectorAll("*").forEach((input) => { this.querySelectorAll("*").forEach((input) => {
if (isAkControl(input) && !input.getAttribute("name")) { if (isAkControl(input) && !input.getAttribute("name")) {
input.setAttribute("name", this.name); input.setAttribute("name", this.name);

View File

@ -163,8 +163,7 @@ export class NotificationDrawer extends AKElement {
} }
renderEmpty() { renderEmpty() {
return html`<ak-empty-state return html`<ak-empty-state header=${msg("No notifications found.")}>
><span slot="header">${msg("No notifications found.")}</span>
<div slot="body">${msg("You don't have any notifications currently.")}</div> <div slot="body">${msg("You don't have any notifications currently.")}</div>
</ak-empty-state>`; </ak-empty-state>`;
} }

View File

@ -288,9 +288,7 @@ export abstract class Table<T> extends AKElement implements TableLike {
return html`<tr role="row"> return html`<tr role="row">
<td role="cell" colspan="25"> <td role="cell" colspan="25">
<div class="pf-l-bullseye"> <div class="pf-l-bullseye">
<ak-empty-state loading <ak-empty-state loading header=${msg("Loading")}></ak-empty-state>
><span slot="header">${msg("Loading")}</span></ak-empty-state
>
</div> </div>
</td> </td>
</tr>`; </tr>`;
@ -302,9 +300,8 @@ export abstract class Table<T> extends AKElement implements TableLike {
<td role="cell" colspan="8"> <td role="cell" colspan="8">
<div class="pf-l-bullseye"> <div class="pf-l-bullseye">
${inner ?? ${inner ??
html`<ak-empty-state html`<ak-empty-state header="${msg("No objects found.")}"
><span slot="header">${msg("No objects found.")}</span> > ><div slot="primary">${this.renderObjectCreate()}</div>
<div slot="primary">${this.renderObjectCreate()}</div>
</ak-empty-state>`} </ak-empty-state>`}
</div> </div>
</td> </td>
@ -319,8 +316,7 @@ export abstract class Table<T> extends AKElement implements TableLike {
renderError(): SlottedTemplateResult { renderError(): SlottedTemplateResult {
if (!this.error) return nothing; if (!this.error) return nothing;
return html`<ak-empty-state icon="fa-ban" return html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-ban">
><span slot="header">${msg("Failed to fetch objects.")}</span>
<div slot="body">${pluckErrorDetail(this.error)}</div> <div slot="body">${pluckErrorDetail(this.error)}</div>
</ak-empty-state>`; </ak-empty-state>`;
} }

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