Compare commits
45 Commits
flows/buff
...
providers/
Author | SHA1 | Date | |
---|---|---|---|
faf8bf591f | |||
52115f9345 | |||
b476551f13 | |||
f9563c25cd | |||
0067e6e155 | |||
ce183929d4 | |||
2fdf345271 | |||
bbcf8418b4 | |||
dc57be46f4 | |||
d68b3ba516 | |||
a9c46cfcbd | |||
c50353ebf6 | |||
db6be9e1b6 | |||
a74892886d | |||
74cd4c2236 | |||
ef3bd7e77b | |||
3f5ad2baa4 | |||
24805f087b | |||
9464b422a3 | |||
da6d4ede51 | |||
cecad5bfd3 | |||
bc4b07d57b | |||
e85d2d0096 | |||
be1dd3103b | |||
5dfde5e1d3 | |||
7cb1e6d81e | |||
d7c3129b1c | |||
2a1d33021b | |||
f273e49ae6 | |||
cc31957900 | |||
b1ccdecc8e | |||
34031003a4 | |||
055e1d1025 | |||
59a804273e | |||
bce70a1796 | |||
e86c40a00c | |||
20e07486ee | |||
0cb7cf2c96 | |||
07736a90b2 | |||
ec28a86259 | |||
260800c60b | |||
ee4780394d | |||
23b746941f | |||
3c2ce40afd | |||
2aceed285e |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2025.6.1
|
current_version = 2025.6.2
|
||||||
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*))?
|
||||||
|
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -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', 'web/src/**', 'web/packages/sfe/src/**') }}-b
|
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
|
||||||
- name: prepare web ui
|
- 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
|
||||||
|
@ -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.4-slim-bookworm-fips AS python-base
|
FROM ghcr.io/goauthentik/fips-python:3.13.5-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" \
|
||||||
|
2
Makefile
2
Makefile
@ -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 > blueprints/schema.json
|
uv run ak make_blueprint_schema --file 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 \
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
__version__ = "2025.6.1"
|
__version__ = "2025.6.2"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,20 +72,33 @@ class Command(BaseCommand):
|
|||||||
"additionalProperties": True,
|
"additionalProperties": True,
|
||||||
},
|
},
|
||||||
"entries": {
|
"entries": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {"$ref": "#/$defs/blueprint_entry"},
|
||||||
"oneOf": [],
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/$defs/blueprint_entry"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"$defs": {},
|
},
|
||||||
|
"$defs": {"blueprint_entry": {"oneOf": []}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("--file", type=str)
|
||||||
|
|
||||||
@no_translations
|
@no_translations
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, file: str, **options):
|
||||||
"""Generate JSON Schema for blueprints"""
|
"""Generate JSON Schema for blueprints"""
|
||||||
self.build()
|
self.build()
|
||||||
self.stdout.write(dumps(self.schema, indent=4, default=Command.json_default))
|
with open(file, "w") as _schema:
|
||||||
|
_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:
|
||||||
@ -112,7 +125,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["properties"]["entries"]["items"]["oneOf"].append(
|
self.schema["$defs"]["blueprint_entry"]["oneOf"].append(
|
||||||
self.template_entry(model_path, model, serializer)
|
self.template_entry(model_path, model, serializer)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
version: 1
|
version: 1
|
||||||
entries:
|
entries:
|
||||||
|
foo:
|
||||||
- identifiers:
|
- identifiers:
|
||||||
name: "%(id)s"
|
name: "%(id)s"
|
||||||
slug: "%(id)s"
|
slug: "%(id)s"
|
||||||
|
@ -191,11 +191,18 @@ 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] = field(default_factory=list)
|
entries: list[BlueprintEntry] | dict[str, 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"""
|
||||||
@ -226,7 +233,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.entries:
|
for _entry in blueprint.iter_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
|
||||||
|
@ -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.entries:
|
for entry in self._import.iter_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)
|
||||||
|
@ -387,8 +387,7 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.url,
|
response.url,
|
||||||
(
|
(
|
||||||
f"http://localhost#access_token={token.token}"
|
f"http://localhost#id_token={provider.encode(token.id_token.to_dict())}"
|
||||||
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}"
|
||||||
),
|
),
|
||||||
@ -563,7 +562,6 @@ 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",
|
||||||
|
@ -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,7 +630,6 @@ 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
|
||||||
|
@ -20,6 +20,9 @@ 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):
|
||||||
@ -109,10 +112,15 @@ 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=self.executor.plan.context.get("connection_settings", {}),
|
settings=settings or {},
|
||||||
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,
|
||||||
|
@ -190,6 +190,7 @@ 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",
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
# 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -12,6 +12,7 @@ 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,
|
||||||
@ -179,6 +180,9 @@ 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)
|
||||||
|
@ -205,6 +205,13 @@ 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
|
||||||
|
@ -13,6 +13,7 @@ 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,
|
||||||
@ -175,7 +176,9 @@ class AuthNRequestParser:
|
|||||||
|
|
||||||
def idp_initiated(self) -> AuthNRequest:
|
def idp_initiated(self) -> AuthNRequest:
|
||||||
"""Create IdP Initiated AuthNRequest"""
|
"""Create IdP Initiated AuthNRequest"""
|
||||||
relay_state = None
|
request = AuthNRequest(relay_state=None)
|
||||||
if self.provider.default_relay_state != "":
|
if self.provider.default_relay_state != "":
|
||||||
relay_state = self.provider.default_relay_state
|
request.relay_state = self.provider.default_relay_state
|
||||||
return AuthNRequest(relay_state=relay_state)
|
if self.provider.default_name_id_policy != SAMLNameIDPolicy.UNSPECIFIED:
|
||||||
|
request.name_id_policy = self.provider.default_name_id_policy
|
||||||
|
return request
|
||||||
|
@ -13,6 +13,7 @@ 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,
|
||||||
@ -46,6 +47,7 @@ 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
|
||||||
|
|
||||||
@ -60,6 +62,7 @@ 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()
|
||||||
@ -148,6 +151,11 @@ 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,
|
||||||
@ -155,4 +163,5 @@ 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,
|
||||||
)
|
)
|
||||||
|
@ -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:unspecified</md:NameIDFormat>
|
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</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" />
|
||||||
|
@ -14,6 +14,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@ -86,6 +87,7 @@ 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)),
|
||||||
|
@ -166,6 +166,7 @@ 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,
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
# 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.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -39,6 +39,7 @@ 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,
|
||||||
@ -73,6 +74,7 @@ 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
@ -2,7 +2,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "authentik 2025.6.1 Blueprint schema",
|
"title": "authentik 2025.6.2 Blueprint schema",
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"entries"
|
"entries"
|
||||||
@ -38,8 +38,27 @@
|
|||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"entries": {
|
"entries": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
"$ref": "#/$defs/blueprint_entry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/blueprint_entry"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$defs": {
|
||||||
|
"blueprint_entry": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -4238,10 +4257,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"$defs": {
|
|
||||||
"model_authentik_blueprints.blueprintinstance": {
|
"model_authentik_blueprints.blueprintinstance": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -9217,6 +9233,18 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Default relay state",
|
"title": "Default relay state",
|
||||||
"description": "Default relay_state value for IDP-initiated logins"
|
"description": "Default relay_state value for IDP-initiated logins"
|
||||||
|
},
|
||||||
|
"default_name_id_policy": {
|
||||||
|
"type": "string",
|
||||||
|
"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"
|
||||||
|
],
|
||||||
|
"title": "Default name id policy"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
@ -11639,7 +11667,8 @@
|
|||||||
"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:1.1:nameid-format:X509SubjectName",
|
"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:WindowsDomainQualifiedName",
|
||||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
|
||||||
|
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
||||||
],
|
],
|
||||||
"title": "Name id policy",
|
"title": "Name id policy",
|
||||||
"description": "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."
|
"description": "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."
|
||||||
|
@ -31,7 +31,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
|
||||||
restart: unless-stopped
|
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.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
4
go.mod
4
go.mod
@ -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.3.0
|
github.com/jellydator/ttlcache/v3 v3.4.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.2025061.2
|
goauthentik.io/api/v3 v3.2025062.1
|
||||||
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
|
||||||
|
8
go.sum
8
go.sum
@ -203,8 +203,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
|||||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
github.com/jcmturner/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.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
|
github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY=
|
||||||
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
|
github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 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 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.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.2025061.2 h1:bKmrl82Gz6J8lz3f+QIH9g+MEkl3MvkMXF34GktesA0=
|
goauthentik.io/api/v3 v3.2025062.1 h1:spvILDpDDWJNO3pM6QGqmryx6NvSchr1E8H60J/XUCA=
|
||||||
goauthentik.io/api/v3 v3.2025061.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2025062.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-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=
|
||||||
|
@ -33,4 +33,4 @@ func UserAgent() string {
|
|||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2025.6.1"
|
const VERSION = "2025.6.2"
|
||||||
|
@ -26,7 +26,7 @@ Parameters:
|
|||||||
Description: authentik Docker image
|
Description: authentik Docker image
|
||||||
AuthentikVersion:
|
AuthentikVersion:
|
||||||
Type: String
|
Type: String
|
||||||
Default: 2025.6.1
|
Default: 2025.6.2
|
||||||
Description: authentik Docker image tag
|
Description: authentik Docker image tag
|
||||||
AuthentikServerCPU:
|
AuthentikServerCPU:
|
||||||
Type: Number
|
Type: Number
|
||||||
|
@ -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-05-28 11:25+0000\n"
|
"POT-Creation-Date: 2025-06-04 00:12+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
|
"Last-Translator: Iamanaws, 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 ""
|
msgstr "Certificados utilizados para la autenticación del cliente."
|
||||||
|
|
||||||
#: 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 el grupo como padre de sí mismo."
|
msgstr "No se puede establecer un grupo como su propio padre."
|
||||||
|
|
||||||
#: 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 "Habiliar estado de \"superusuario\""
|
msgstr "Habilitar el estado de superusuario"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Disable superuser status"
|
msgid "Disable superuser status"
|
||||||
msgstr "Deshabiliar estado de \"superusuario\""
|
msgstr "Deshabilitar el 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 usado para terminar la sesión de un proveedor."
|
msgstr "Flujo utilizado para finalizar la sesión desde 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 ""
|
msgstr "Derecho de Aplicación"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application Entitlements"
|
msgid "Application Entitlements"
|
||||||
msgstr ""
|
msgstr "Derechos de Aplicación"
|
||||||
|
|
||||||
#: 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 ""
|
||||||
"Apunta a un usuario con una dirección de correo electrónico idéntica. Puede "
|
"Enlace a un usuario con la misma dirección de correo electrónico. Puede "
|
||||||
"tener implicaciones de seguridad cuando una fuente no valida la dirección de"
|
"tener implicaciones de seguridad cuando una fuente no valida las direcciones"
|
||||||
" correo electrónico."
|
" de 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 un nombre de usuario idéntico. Puede tener "
|
"Enlace a un usuario con el mismo nombre de usuario. Puede tener "
|
||||||
"implicaciones de seguridad cuando se usa un nombre de usuario con otra "
|
"implicaciones de seguridad cuando un nombre de usuario se utiliza 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 un nombre idéntico. Puede tener implicaciones de "
|
"Enlace a un grupo con el mismo nombre. Puede tener implicaciones de "
|
||||||
"seguridad cuando se utiliza un nombre de grupo con otra fuente."
|
"seguridad cuando un nombre de grupo se utiliza 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 ""
|
msgstr "datos de sesión"
|
||||||
|
|
||||||
#: 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} vinculado exitosamente!"
|
msgstr "¡{source} enlazado correctamente!"
|
||||||
|
|
||||||
#: 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 de claves de certificado"
|
msgstr "Par Certificado-Clave"
|
||||||
|
|
||||||
#: authentik/crypto/models.py
|
#: authentik/crypto/models.py
|
||||||
msgid "Certificate-Key Pairs"
|
msgid "Certificate-Key Pairs"
|
||||||
msgstr "Pares de claves de certificado"
|
msgstr "Pares Certificado-Clave"
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "Número de contraseñas contra las que verificar."
|
||||||
|
|
||||||
#: 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,18 +521,20 @@ 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 ""
|
msgstr "Política de Unicidad de Contraseñas"
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "Password Uniqueness Policies"
|
msgid "Password Uniqueness Policies"
|
||||||
msgstr ""
|
msgstr "Políticas de Unicidad de Contraseñas"
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "User Password History"
|
msgid "User Password History"
|
||||||
msgstr ""
|
msgstr "Historial de Contraseñas del Usuario"
|
||||||
|
|
||||||
#: authentik/enterprise/policy.py
|
#: authentik/enterprise/policy.py
|
||||||
msgid "Enterprise required to access this feature."
|
msgid "Enterprise required to access this feature."
|
||||||
@ -617,39 +619,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 ""
|
msgstr "Clave utilizada para firmar los eventos SSF."
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Provider"
|
msgid "Shared Signals Framework Provider"
|
||||||
msgstr ""
|
msgstr "Proveedor del Marco de Señales Compartidas"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Providers"
|
msgid "Shared Signals Framework Providers"
|
||||||
msgstr ""
|
msgstr "Proveedores del Marco de Señales Compartidas"
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "Agregar flujo de datos al proveedor SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream"
|
msgid "SSF Stream"
|
||||||
msgstr ""
|
msgstr "Flujo de Datos SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Streams"
|
msgid "SSF Streams"
|
||||||
msgstr ""
|
msgstr "Flujos de Datos SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Event"
|
msgid "SSF Stream Event"
|
||||||
msgstr ""
|
msgstr "Evento de Flujo de Datos SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Events"
|
msgid "SSF Stream Events"
|
||||||
msgstr ""
|
msgstr "Eventos de Flujos de Datos SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/tasks.py
|
#: authentik/enterprise/providers/ssf/tasks.py
|
||||||
msgid "Failed to send request"
|
msgid "Failed to send request"
|
||||||
msgstr "Falló envio de petición"
|
msgstr "Error al enviar la solicitud"
|
||||||
|
|
||||||
#: 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"
|
||||||
@ -681,26 +683,29 @@ 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 ""
|
msgstr "Etapa de TLS mutuo"
|
||||||
|
|
||||||
#: authentik/enterprise/stages/mtls/models.py
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
msgid "Mutual TLS Stages"
|
msgid "Mutual TLS Stages"
|
||||||
msgstr ""
|
msgstr "Etapas de TLS mutuo"
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "Permisos para pasar Certificados a los puestos avanzados."
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "Se requiere certificado, pero no se proporcionó ninguno."
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "No se encontró usuario para el certificado."
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -753,12 +758,16 @@ 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 ""
|
||||||
@ -786,7 +795,7 @@ msgstr "Transporte de notificaciones"
|
|||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid "Notification Transports"
|
msgid "Notification Transports"
|
||||||
msgstr "Transportes de notificación"
|
msgstr "Medios de Notificación"
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid "Notice"
|
msgid "Notice"
|
||||||
@ -813,9 +822,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 ""
|
||||||
"Seleccione qué transportes se deben usar para notificar al usuario. Si no se"
|
"Selecciona qué medios 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 "
|
||||||
"usuario de authentik."
|
"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."
|
||||||
@ -987,7 +996,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 ""
|
msgstr "Evaluar las políticas cuando la Etapa se presenta al usuario."
|
||||||
|
|
||||||
#: authentik/flows/models.py
|
#: authentik/flows/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -1034,6 +1043,8 @@ 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"
|
||||||
@ -1041,20 +1052,21 @@ 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 ""
|
msgstr "Sincronizando usuarios"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
msgid "Syncing groups"
|
msgid "Syncing groups"
|
||||||
msgstr ""
|
msgstr "Sincronizando grupos"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Syncing page {page} of groups"
|
msgid "Syncing page {page} of {object_type}"
|
||||||
msgstr "Sincronizando página {page} de grupos"
|
msgstr "Sincronizando página {page} de {object_type}"
|
||||||
|
|
||||||
#: 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
|
||||||
@ -1233,7 +1245,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 caducado."
|
msgstr "La contraseña ha expirado."
|
||||||
|
|
||||||
#: authentik/policies/expiry/models.py
|
#: authentik/policies/expiry/models.py
|
||||||
msgid "Password Expiry Policy"
|
msgid "Password Expiry Policy"
|
||||||
@ -1271,7 +1283,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 previa es mayor que el límite."
|
msgstr "La distancia desde la autenticación anterior es mayor que el umbral."
|
||||||
|
|
||||||
#: authentik/policies/geoip/models.py
|
#: authentik/policies/geoip/models.py
|
||||||
msgid "Distance is further than possible."
|
msgid "Distance is further than possible."
|
||||||
@ -1320,7 +1332,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 ""
|
||||||
@ -1594,11 +1606,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 ""
|
msgstr "ES384 (Encriptación Asimétrica)"
|
||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "ES512 (Asymmetric Encryption)"
|
msgid "ES512 (Asymmetric Encryption)"
|
||||||
msgstr ""
|
msgstr "ES512 (Encriptación Asimétrica)"
|
||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "Scope used by the client"
|
msgid "Scope used by the client"
|
||||||
@ -1813,7 +1825,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 de host interno"
|
msgstr "Validación SSL del host interno"
|
||||||
|
|
||||||
#: authentik/providers/proxy/models.py
|
#: authentik/providers/proxy/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -2027,7 +2039,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid "AuthnContextClassRef Property Mapping"
|
msgid "AuthnContextClassRef Property Mapping"
|
||||||
msgstr ""
|
msgstr "Asignación de Propiedades de AuthnContextClassRef"
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -2035,6 +2047,9 @@ 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 ""
|
||||||
@ -2184,11 +2199,11 @@ msgstr "Predeterminado"
|
|||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "AWS"
|
msgid "AWS"
|
||||||
msgstr ""
|
msgstr "AWS"
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "Slack"
|
msgid "Slack"
|
||||||
msgstr ""
|
msgstr "Slack"
|
||||||
|
|
||||||
#: 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"
|
||||||
@ -2200,11 +2215,13 @@ msgstr "Token de Autenticación"
|
|||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "SCIM Compatibility Mode"
|
msgid "SCIM Compatibility Mode"
|
||||||
msgstr ""
|
msgstr "Modo de Compatibilidad SCIM"
|
||||||
|
|
||||||
#: 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"
|
||||||
@ -2232,7 +2249,7 @@ msgstr "Roles"
|
|||||||
|
|
||||||
#: authentik/rbac/models.py
|
#: authentik/rbac/models.py
|
||||||
msgid "Initial Permissions"
|
msgid "Initial Permissions"
|
||||||
msgstr ""
|
msgstr "Permisos Iniciales"
|
||||||
|
|
||||||
#: authentik/rbac/models.py
|
#: authentik/rbac/models.py
|
||||||
msgid "System permission"
|
msgid "System permission"
|
||||||
@ -2270,7 +2287,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/recovery/views.py
|
#: authentik/recovery/views.py
|
||||||
msgid "Used recovery-link to authenticate."
|
msgid "Used recovery-link to authenticate."
|
||||||
msgstr "Se usó el enlace de recuperación para autenticarse."
|
msgstr "Se utilizó un enlace de recuperación para autenticarse."
|
||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid "Kerberos realm"
|
msgid "Kerberos realm"
|
||||||
@ -2282,7 +2299,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 ""
|
msgstr "Tipo de servidor KAdmin"
|
||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid "Sync users from Kerberos into authentik"
|
msgid "Sync users from Kerberos into authentik"
|
||||||
@ -2290,23 +2307,24 @@ 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 "Cuando un usuario cambia su contraseña, sincronizarlo hacia Kerberos"
|
msgstr ""
|
||||||
|
"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 como kadmin para la sincronización."
|
msgstr "Principal para autenticarse en 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 como kadmin para la sincronización"
|
msgstr "Contraseña para autenticarse en 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 como kadmin para la sincronización. Debe estar "
|
"Keytab para autenticarse en kadmin para la sincronización. Debe estar "
|
||||||
"codificado en base64 o en el formato TIPO:residual"
|
"codificado en base64 o en el formato TIPO:residuo"
|
||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -2322,7 +2340,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@nombredelservidor"
|
" el formato HTTP@nombre_de_host"
|
||||||
|
|
||||||
#: 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"
|
||||||
@ -2339,8 +2357,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 por authentik será actualizada "
|
"Si está habilitado, la contraseña almacenada en authentik se actualizará al "
|
||||||
"al iniciar sesión con el backend de contraseñas Kerberos"
|
"iniciar sesión con el backend de contraseñas de Kerberos."
|
||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid "Kerberos Source"
|
msgid "Kerberos Source"
|
||||||
@ -2388,7 +2406,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
" Asegúrate de que tienes entradas válidas\n"
|
" Asegúrate de que tienes entradas válidas\n"
|
||||||
" (se obtienen a través de kinit) \n"
|
" (obtenibles mediante 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"
|
||||||
" "
|
" "
|
||||||
@ -2453,6 +2471,10 @@ 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."
|
||||||
@ -2485,12 +2507,17 @@ 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"
|
||||||
@ -2512,22 +2539,24 @@ 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 ""
|
msgstr "Conexión de Fuente LDAP de Usuario"
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connections"
|
msgid "User LDAP Source Connections"
|
||||||
msgstr ""
|
msgstr "Conexiones de Fuente LDAP de Usuario"
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "Group LDAP Source Connection"
|
msgid "Group LDAP Source Connection"
|
||||||
msgstr ""
|
msgstr "Conexión de Fuente LDAP de Grupo"
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "Group LDAP Source Connections"
|
msgid "Group LDAP Source Connections"
|
||||||
msgstr ""
|
msgstr "Conexiones de Fuente LDAP de Grupo"
|
||||||
|
|
||||||
#: 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."
|
||||||
@ -2539,11 +2568,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 ""
|
msgstr "Autenticación Básica HTTP"
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "Incluir el ID de cliente y el secreto como parámetros de la solicitud"
|
||||||
|
|
||||||
#: authentik/sources/oauth/models.py
|
#: authentik/sources/oauth/models.py
|
||||||
msgid "Request Token URL"
|
msgid "Request Token URL"
|
||||||
@ -2590,6 +2619,8 @@ 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"
|
||||||
@ -2907,7 +2938,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 ""
|
msgstr "Continuar a {source_name}"
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
@ -2943,7 +2974,7 @@ msgstr "Dispositivos Duo"
|
|||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
msgid "Email OTP"
|
msgid "Email OTP"
|
||||||
msgstr ""
|
msgstr "OTP por Correo Electrónico"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
@ -2964,11 +2995,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 ""
|
msgstr "Etapa de Configuración del Autenticador de Correo Electrónico"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
msgid "Email Authenticator Setup Stages"
|
msgid "Email Authenticator Setup Stages"
|
||||||
msgstr ""
|
msgstr "Etapas de Configuración del Autenticador de Correo Electrónico"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
#: authentik/stages/authenticator_email/stage.py
|
#: authentik/stages/authenticator_email/stage.py
|
||||||
@ -2979,11 +3010,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
msgid "Email Device"
|
msgid "Email Device"
|
||||||
msgstr "Dispositivo de Email"
|
msgstr "Dispositivo de correo electrónico"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
msgid "Email Devices"
|
msgid "Email Devices"
|
||||||
msgstr "Dispositivos de Email"
|
msgstr "Dispositivos de correo electrónico"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/stage.py
|
#: authentik/stages/authenticator_email/stage.py
|
||||||
#: authentik/stages/authenticator_sms/stage.py
|
#: authentik/stages/authenticator_sms/stage.py
|
||||||
@ -2993,7 +3024,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 "Email Inválido"
|
msgstr "Correo electrónico 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
|
||||||
@ -3013,6 +3044,9 @@ 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
|
||||||
@ -3022,7 +3056,8 @@ 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."
|
" Si no solicitaste este código, por favor ignora este correo. El código anterior es válido por %(expires)s.\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: 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
|
||||||
@ -3035,6 +3070,8 @@ 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
|
||||||
@ -3276,8 +3313,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 ""
|
||||||
"Compensación después de la cual caduca el consentimiento. (Formato: horas = "
|
"Desfase después del cual expira el consentimiento. (Formato: "
|
||||||
"1; minutos = 2; segundos = 3)."
|
"hours=1;minutes=2;seconds=3)."
|
||||||
|
|
||||||
#: authentik/stages/consent/models.py
|
#: authentik/stages/consent/models.py
|
||||||
msgid "Consent Stage"
|
msgid "Consent Stage"
|
||||||
@ -3297,7 +3334,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 ""
|
msgstr "Token de consentimiento inválido, mostrando el aviso nuevamente"
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
@ -3317,11 +3354,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 ""
|
msgstr "Continúa para confirmar esta dirección de correo electrónico."
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "El enlace ya fue utilizado, por favor, solícita uno nuevo."
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
@ -3445,7 +3482,8 @@ 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."
|
" Si no solicitaste un cambio de contraseña, por favor ignora este correo. El enlace anterior es válido por %(expires)s.\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: authentik/stages/email/templates/email/password_reset.txt
|
#: authentik/stages/email/templates/email/password_reset.txt
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -3529,24 +3567,26 @@ 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 está vinculado en la parte inferior de la"
|
"Flujo de inscripción opcional, que se enlaza 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 está vinculado en la parte inferior de "
|
"Flujo de recuperación opcional, que se enlaza en la parte inferior de la "
|
||||||
"la página."
|
"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 sin contraseña opcional, el cual está vinculado en la parte inferior "
|
"Flujo opcional sin contraseña, que se enlaza en la parte inferior de la "
|
||||||
"de la página."
|
"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."
|
||||||
@ -3780,11 +3820,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 ""
|
msgstr "La URL de destino debe estar presente cuando el modo es Estático."
|
||||||
|
|
||||||
#: 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 ""
|
msgstr "El Flujo de Destino debe estar presente cuando el modo es Flujo."
|
||||||
|
|
||||||
#: authentik/stages/redirect/models.py
|
#: authentik/stages/redirect/models.py
|
||||||
msgid "Redirect Stage"
|
msgid "Redirect Stage"
|
||||||
@ -3841,10 +3881,6 @@ 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"
|
||||||
@ -3920,10 +3956,12 @@ 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."
|
||||||
@ -3946,8 +3984,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 a los administradores proporcionar una razón para suplantar un "
|
"Requerir que los administradores proporcionen una razón para personificar a "
|
||||||
"usuario."
|
"un usuario."
|
||||||
|
|
||||||
#: authentik/tenants/models.py
|
#: authentik/tenants/models.py
|
||||||
msgid "Default token duration"
|
msgid "Default token duration"
|
||||||
@ -3959,7 +3997,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
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.6.1",
|
"version": "2025.6.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.6.1",
|
"version": "2025.6.2",
|
||||||
"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",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.6.1",
|
"version": "2025.6.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
182
packages/eslint-config/package-lock.json
generated
182
packages/eslint-config/package-lock.json
generated
@ -216,9 +216,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/config-array": {
|
"node_modules/@eslint/config-array": {
|
||||||
"version": "0.20.0",
|
"version": "0.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
|
||||||
"integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
|
"integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
|
||||||
"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.28.0",
|
"version": "9.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
|
||||||
"integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==",
|
"integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||||
"integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
|
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
||||||
"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.0",
|
"@typescript-eslint/scope-manager": "8.34.1",
|
||||||
"@typescript-eslint/type-utils": "8.34.0",
|
"@typescript-eslint/type-utils": "8.34.1",
|
||||||
"@typescript-eslint/utils": "8.34.0",
|
"@typescript-eslint/utils": "8.34.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||||
"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.0",
|
"@typescript-eslint/parser": "^8.34.1",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
||||||
"integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==",
|
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.34.0",
|
"@typescript-eslint/scope-manager": "8.34.1",
|
||||||
"@typescript-eslint/types": "8.34.0",
|
"@typescript-eslint/types": "8.34.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.34.0",
|
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
||||||
"integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==",
|
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.34.0",
|
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
||||||
"@typescript-eslint/types": "^8.34.0",
|
"@typescript-eslint/types": "^8.34.1",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
||||||
"integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==",
|
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.34.0",
|
"@typescript-eslint/types": "8.34.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.0"
|
"@typescript-eslint/visitor-keys": "8.34.1"
|
||||||
},
|
},
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
||||||
"integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==",
|
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
||||||
"integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==",
|
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.34.0",
|
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||||
"@typescript-eslint/utils": "8.34.0",
|
"@typescript-eslint/utils": "8.34.1",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
||||||
"integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
|
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
||||||
"integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==",
|
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.34.0",
|
"@typescript-eslint/project-service": "8.34.1",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.34.0",
|
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
||||||
"@typescript-eslint/types": "8.34.0",
|
"@typescript-eslint/types": "8.34.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||||
"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.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
||||||
"integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==",
|
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
||||||
"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.0",
|
"@typescript-eslint/scope-manager": "8.34.1",
|
||||||
"@typescript-eslint/types": "8.34.0",
|
"@typescript-eslint/types": "8.34.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.34.0"
|
"@typescript-eslint/typescript-estree": "8.34.1"
|
||||||
},
|
},
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
||||||
"integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==",
|
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.34.0",
|
"@typescript-eslint/types": "8.34.1",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"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.14.1",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
@ -1554,18 +1554,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.28.0",
|
"version": "9.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
|
||||||
"integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==",
|
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||||
"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.0",
|
"@eslint/config-array": "^0.20.1",
|
||||||
"@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.28.0",
|
"@eslint/js": "9.29.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.3.0",
|
"eslint-scope": "^8.4.0",
|
||||||
"eslint-visitor-keys": "^4.2.0",
|
"eslint-visitor-keys": "^4.2.1",
|
||||||
"espree": "^10.3.0",
|
"espree": "^10.4.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.3.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||||
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
|
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||||
"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.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||||
"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.3.0",
|
"version": "10.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||||
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
|
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.14.0",
|
"acorn": "^8.15.0",
|
||||||
"acorn-jsx": "^5.3.2",
|
"acorn-jsx": "^5.3.2",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"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.0",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
||||||
"integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==",
|
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.34.0",
|
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||||
"@typescript-eslint/parser": "8.34.0",
|
"@typescript-eslint/parser": "8.34.1",
|
||||||
"@typescript-eslint/utils": "8.34.0"
|
"@typescript-eslint/utils": "8.34.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2025.6.1"
|
version = "2025.6.2"
|
||||||
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.5",
|
"pydantic==2.11.7",
|
||||||
"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.5.2",
|
"webauthn==2.6.0",
|
||||||
"wsproto==1.2.0",
|
"wsproto==1.2.0",
|
||||||
"xmlsec==1.3.15",
|
"xmlsec==1.3.15",
|
||||||
"zxcvbn==4.5.0",
|
"zxcvbn==4.5.0",
|
||||||
|
43
schema.yml
43
schema.yml
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2025.6.1
|
version: 2025.6.2
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@goauthentik.io
|
email: hello@goauthentik.io
|
||||||
@ -22454,6 +22454,17 @@ 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:
|
||||||
@ -29670,6 +29681,7 @@ 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
|
||||||
@ -48745,14 +48757,6 @@ 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
|
||||||
@ -54501,6 +54505,8 @@ 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
|
||||||
@ -54594,7 +54600,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/NameIdPolicyEnum'
|
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
|
||||||
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:
|
||||||
@ -57305,6 +57311,15 @@ 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
|
||||||
@ -57522,6 +57537,8 @@ 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
|
||||||
@ -57694,6 +57711,8 @@ 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
|
||||||
@ -57802,7 +57821,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/NameIdPolicyEnum'
|
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
|
||||||
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:
|
||||||
@ -57992,7 +58011,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/NameIdPolicyEnum'
|
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
|
||||||
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:
|
||||||
|
@ -7,7 +7,7 @@ services:
|
|||||||
network_mode: host
|
network_mode: host
|
||||||
restart: always
|
restart: always
|
||||||
mailpit:
|
mailpit:
|
||||||
image: docker.io/axllent/mailpit:v1.26.0
|
image: docker.io/axllent/mailpit:v1.26.1
|
||||||
ports:
|
ports:
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
|
18
uv.lock
generated
18
uv.lock
generated
@ -165,7 +165,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2025.6.1"
|
version = "2025.6.2"
|
||||||
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.5" },
|
{ name = "pydantic", specifier = "==2.11.7" },
|
||||||
{ 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.5.2" },
|
{ name = "webauthn", specifier = "==2.6.0" },
|
||||||
{ 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.5"
|
version = "2.11.7"
|
||||||
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/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" }
|
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" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ 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" },
|
{ 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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
@ -3391,7 +3391,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webauthn"
|
name = "webauthn"
|
||||||
version = "2.5.2"
|
version = "2.6.0"
|
||||||
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/8d/92/8d2a4eec83d8e7feacdaad37c6eb6eb922100cecce5c14a41d8069a59a03/webauthn-2.5.2.tar.gz", hash = "sha256:09c13dfc1c68c810f32fa4d89b1d37acb9f9ae9091c9d7019e313be4525a95ef", size = 124114, upload-time = "2025-03-07T19:44:05.243Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/63/38/5792cb2034673c162a721df0ad65825699516ee0c938a65670ad3cdabf6c/webauthn-2.6.0.tar.gz", hash = "sha256:13cf5b009a64cef569599ffecf24550df1d7c0cd4fbaea870f937148484a80b4", size = 123608, upload-time = "2025-06-16T22:25:26.76Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/7f/fe/f6ae41de9f383439e30b303a67f6f45d2fceabedaedc34c62f74d58c5c73/webauthn-2.5.2-py3-none-any.whl", hash = "sha256:44246e496e617eb5e2f51165046b9f0197fcdf470f69cd6734061a27ba365f8e", size = 71624, upload-time = "2025-03-07T19:44:03.728Z" },
|
{ url = "https://files.pythonhosted.org/packages/56/c5/b1bba7f6a50caca77f37003e098f48f8dc68d990aba8a03ac8376016430b/webauthn-2.6.0-py3-none-any.whl", hash = "sha256:459973eb5780c1f41bec42b682acf587456b185733398a0b99a0714705b79447", size = 71189, upload-time = "2025-06-16T22:25:25.535Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
1703
web/package-lock.json
generated
1703
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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.1-1749515784",
|
"@goauthentik/api": "^2025.6.2-1750112513",
|
||||||
"@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,10 +102,9 @@
|
|||||||
"@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.28.1",
|
"@sentry/browser": "^9.30.0",
|
||||||
"@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",
|
||||||
@ -137,6 +136,7 @@
|
|||||||
"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.4",
|
"@wdio/browser-runner": "9.15",
|
||||||
"@wdio/cli": "9.4",
|
"@wdio/cli": "9.15",
|
||||||
"@wdio/spec-reporter": "^9.1.2",
|
"@wdio/spec-reporter": "^9.15.0",
|
||||||
"@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.28.0",
|
"eslint": "^9.29.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.0",
|
"typescript-eslint": "^8.34.1",
|
||||||
"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"
|
||||||
|
2
web/packages/core/types/node.d.ts
vendored
2
web/packages/core/types/node.d.ts
vendored
@ -14,7 +14,7 @@ declare module "module" {
|
|||||||
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var __dirname: string;
|
var __dirname: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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,47 +257,9 @@ 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;
|
||||||
@ -310,98 +272,6 @@ 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];
|
||||||
@ -505,8 +375,8 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
|||||||
`);
|
`);
|
||||||
navigator.credentials
|
navigator.credentials
|
||||||
.get({
|
.get({
|
||||||
publicKey: this.transformCredentialRequestOptions(
|
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
|
||||||
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptions,
|
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptionsJSON,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
.then((assertion) => {
|
.then((assertion) => {
|
||||||
@ -514,15 +384,9 @@ 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: transformedAssertionForServer,
|
webauthn: (assertion as PublicKeyCredential).toJSON(),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Error when validating assertion on server: ${err}`);
|
throw new Error(`Error when validating assertion on server: ${err}`);
|
||||||
|
@ -88,7 +88,8 @@ export class RecentEventsCard extends Table<Event> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@ 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";
|
||||||
@ -130,14 +131,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-text-input
|
<ak-slug-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-text-input>
|
></ak-slug-input>
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="group"
|
name="group"
|
||||||
value=${ifDefined(this.instance?.group)}
|
value=${ifDefined(this.instance?.group)}
|
||||||
|
@ -117,13 +117,11 @@ 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")}
|
||||||
|
@ -115,7 +115,8 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
|||||||
.columns=${COLUMNS}
|
.columns=${COLUMNS}
|
||||||
.content=${[]}
|
.content=${[]}
|
||||||
></ak-select-table>
|
></ak-select-table>
|
||||||
<ak-empty-state header=${msg("No bound policies.")} icon="pf-icon-module">
|
<ak-empty-state icon="pf-icon-module"
|
||||||
|
><span slot="header">${msg("No bound policies.")} </span>
|
||||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<button
|
<button
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/components/ak-private-textarea-input.js";
|
import "@goauthentik/components/ak-secret-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-private-textarea-input
|
<ak-secret-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-private-textarea-input>
|
></ak-secret-textarea-input>
|
||||||
<ak-private-textarea-input
|
<ak-secret-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-private-textarea-input>`;
|
></ak-secret-textarea-input>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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-private-textarea-input.js";
|
import "@goauthentik/components/ak-secret-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-private-textarea-input
|
<ak-secret-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-private-textarea-input>`;
|
</ak-secret-textarea-input>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,8 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Stages bound")} icon="pf-icon-module">
|
html`<ak-empty-state icon="pf-icon-module">
|
||||||
|
<span slot="header">${msg("No Stages bound")}</span>
|
||||||
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<ak-stage-wizard
|
<ak-stage-wizard
|
||||||
|
@ -3,6 +3,7 @@ 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";
|
||||||
@ -91,17 +92,16 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
|||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p>
|
<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">
|
|
||||||
<input
|
<ak-slug-input
|
||||||
type="text"
|
name="slug"
|
||||||
value="${ifDefined(this.instance?.slug)}"
|
value=${ifDefined(this.instance?.slug)}
|
||||||
class="pf-c-form-control pf-m-monospace"
|
label=${msg("Slug")}
|
||||||
autocomplete="off"
|
|
||||||
spellcheck="false"
|
|
||||||
required
|
required
|
||||||
/>
|
help=${msg("Visible in the URL.")}
|
||||||
<p class="pf-c-form__helper-text">${msg("Visible in the URL.")}</p>
|
input-hint="code"
|
||||||
</ak-form-element-horizontal>
|
></ak-slug-input>
|
||||||
|
|
||||||
<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}>
|
||||||
|
@ -198,7 +198,8 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Policies bound.")} icon="pf-icon-module">
|
html`<ak-empty-state icon="pf-icon-module"
|
||||||
|
><span slot="header">${msg("No Policies bound.")}</span>
|
||||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<ak-policy-wizard
|
<ak-policy-wizard
|
||||||
|
@ -4,6 +4,7 @@ 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";
|
||||||
@ -68,21 +69,15 @@ 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-form-element-horizontal
|
<ak-hidden-text-input
|
||||||
label=${msg("Client Secret")}
|
|
||||||
required
|
|
||||||
name="clientSecret"
|
name="clientSecret"
|
||||||
>
|
label=${msg("Client Secret")}
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${this.instance?.clientSecret ?? ""}"
|
value="${this.instance?.clientSecret ?? ""}"
|
||||||
class="pf-c-form-control pf-m-monospace"
|
input-hint="code"
|
||||||
required
|
required
|
||||||
/>
|
.help=${msg("Client secret for the app registration.")}
|
||||||
<p class="pf-c-form__helper-text">
|
>
|
||||||
${msg("Client secret for the app registration.")}
|
</ak-hidden-text-input>
|
||||||
</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"
|
||||||
|
@ -5,6 +5,7 @@ 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";
|
||||||
@ -166,17 +167,16 @@ export function renderForm(
|
|||||||
input-hint="code"
|
input-hint="code"
|
||||||
>
|
>
|
||||||
</ak-text-input>
|
</ak-text-input>
|
||||||
<ak-text-input
|
<ak-hidden-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-text-input>
|
</ak-hidden-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
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
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";
|
||||||
@ -74,14 +76,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-text-input
|
<ak-hidden-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-text-input>
|
></ak-hidden-text-input>
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="clientNetworks"
|
name="clientNetworks"
|
||||||
label=${msg("Client Networks")}
|
label=${msg("Client Networks")}
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
PropertymappingsApi,
|
PropertymappingsApi,
|
||||||
PropertymappingsProviderSamlListRequest,
|
PropertymappingsProviderSamlListRequest,
|
||||||
|
SAMLNameIDPolicyEnum,
|
||||||
SAMLPropertyMapping,
|
SAMLPropertyMapping,
|
||||||
SAMLProvider,
|
SAMLProvider,
|
||||||
SpBindingEnum,
|
SpBindingEnum,
|
||||||
@ -316,6 +317,54 @@ 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"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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";
|
||||||
@ -50,7 +51,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
|||||||
>
|
>
|
||||||
</ak-switch-input>
|
</ak-switch-input>
|
||||||
|
|
||||||
<ak-text-input
|
<ak-hidden-text-input
|
||||||
name="token"
|
name="token"
|
||||||
label=${msg("Token")}
|
label=${msg("Token")}
|
||||||
value="${provider?.token ?? ""}"
|
value="${provider?.token ?? ""}"
|
||||||
@ -60,7 +61,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-text-input>
|
></ak-hidden-text-input>
|
||||||
<ak-radio-input
|
<ak-radio-input
|
||||||
name="compatibilityMode"
|
name="compatibilityMode"
|
||||||
label=${msg("Compatibility Mode")}
|
label=${msg("Compatibility Mode")}
|
||||||
|
@ -7,8 +7,9 @@ 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-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
import "@goauthentik/components/ak-private-textarea-input.js";
|
import "@goauthentik/components/ak-secret-textarea-input.js";
|
||||||
|
import "@goauthentik/components/ak-slug-input.js";
|
||||||
import "@goauthentik/components/ak-switch-input";
|
import "@goauthentik/components/ak-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";
|
||||||
@ -87,12 +88,13 @@ 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-text-input
|
<ak-slug-input
|
||||||
name="slug"
|
name="slug"
|
||||||
label=${msg("Slug")}
|
|
||||||
value=${ifDefined(this.instance?.slug)}
|
value=${ifDefined(this.instance?.slug)}
|
||||||
|
label=${msg("Slug")}
|
||||||
required
|
required
|
||||||
></ak-text-input>
|
input-hint="code"
|
||||||
|
></ak-slug-input>
|
||||||
<ak-switch-input
|
<ak-switch-input
|
||||||
name="enabled"
|
name="enabled"
|
||||||
?checked=${this.instance?.enabled ?? true}
|
?checked=${this.instance?.enabled ?? true}
|
||||||
@ -248,22 +250,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-private-text-input
|
<ak-secret-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-private-text-input>
|
></ak-secret-text-input>
|
||||||
<ak-private-textarea-input
|
<ak-secret-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-private-textarea-input>
|
></ak-secret-textarea-input>
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="syncCcache"
|
name="syncCcache"
|
||||||
label=${msg("Sync credentials cache")}
|
label=${msg("Sync credentials cache")}
|
||||||
@ -285,14 +287,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-private-textarea-input
|
<ak-secret-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-private-textarea-input>
|
></ak-secret-textarea-input>
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="spnegoCcache"
|
name="spnegoCcache"
|
||||||
label=${msg("SPNEGO credentials cache")}
|
label=${msg("SPNEGO credentials cache")}
|
||||||
|
@ -2,7 +2,8 @@ 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-private-text-input.js";
|
import "@goauthentik/components/ak-secret-text-input.js";
|
||||||
|
import "@goauthentik/components/ak-slug-input.js";
|
||||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
import "@goauthentik/elements/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";
|
||||||
@ -54,14 +55,15 @@ 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">
|
|
||||||
<input
|
<ak-slug-input
|
||||||
type="text"
|
name="slug"
|
||||||
value="${ifDefined(this.instance?.slug)}"
|
value=${ifDefined(this.instance?.slug)}
|
||||||
class="pf-c-form-control"
|
label=${msg("Slug")}
|
||||||
required
|
required
|
||||||
/>
|
input-hint="code"
|
||||||
</ak-form-element-horizontal>
|
></ak-slug-input>
|
||||||
|
|
||||||
<ak-form-element-horizontal name="enabled">
|
<ak-form-element-horizontal name="enabled">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -260,11 +262,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-private-text-input
|
<ak-secret-text-input
|
||||||
label=${msg("Bind Password")}
|
label=${msg("Bind Password")}
|
||||||
name="bindPassword"
|
name="bindPassword"
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-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"
|
||||||
|
@ -8,8 +8,9 @@ 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";
|
||||||
@ -267,16 +268,13 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
|||||||
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 pf-m-monospace"
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck="false"
|
|
||||||
required
|
required
|
||||||
/>
|
input-hint="code"
|
||||||
</ak-form-element-horizontal>
|
></ak-slug-input>
|
||||||
<ak-form-element-horizontal name="enabled">
|
<ak-form-element-horizontal name="enabled">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -441,14 +439,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-private-textarea-input
|
<ak-secret-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-private-textarea-input>
|
></ak-secret-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"
|
||||||
|
@ -10,6 +10,7 @@ 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";
|
||||||
@ -128,7 +129,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">
|
||||||
@ -183,14 +184,15 @@ 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">
|
|
||||||
<input
|
<ak-slug-input
|
||||||
type="text"
|
name="slug"
|
||||||
value="${ifDefined(this.instance?.slug)}"
|
value=${ifDefined(this.instance?.slug)}
|
||||||
class="pf-c-form-control"
|
label=${msg("Slug")}
|
||||||
required
|
required
|
||||||
/>
|
input-hint="code"
|
||||||
</ak-form-element-horizontal>
|
></ak-slug-input>
|
||||||
|
|
||||||
<ak-form-element-horizontal name="enabled">
|
<ak-form-element-horizontal name="enabled">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
|
@ -9,6 +9,7 @@ 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";
|
||||||
@ -25,7 +26,7 @@ import {
|
|||||||
DigestAlgorithmEnum,
|
DigestAlgorithmEnum,
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
GroupMatchingModeEnum,
|
GroupMatchingModeEnum,
|
||||||
NameIdPolicyEnum,
|
SAMLNameIDPolicyEnum,
|
||||||
SAMLSource,
|
SAMLSource,
|
||||||
SignatureAlgorithmEnum,
|
SignatureAlgorithmEnum,
|
||||||
SourcesApi,
|
SourcesApi,
|
||||||
@ -89,14 +90,15 @@ 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">
|
|
||||||
<input
|
<ak-slug-input
|
||||||
type="text"
|
name="slug"
|
||||||
value="${ifDefined(this.instance?.slug)}"
|
value=${ifDefined(this.instance?.slug)}
|
||||||
class="pf-c-form-control"
|
label=${msg("Slug")}
|
||||||
required
|
required
|
||||||
/>
|
input-hint="code"
|
||||||
</ak-form-element-horizontal>
|
></ak-slug-input>
|
||||||
|
|
||||||
<ak-form-element-horizontal name="enabled">
|
<ak-form-element-horizontal name="enabled">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -351,37 +353,37 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
|||||||
>
|
>
|
||||||
<select class="pf-c-form-control">
|
<select class="pf-c-form-control">
|
||||||
<option
|
<option
|
||||||
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
|
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
|
||||||
?selected=${this.instance?.nameIdPolicy ===
|
?selected=${this.instance?.nameIdPolicy ===
|
||||||
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
|
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
|
||||||
>
|
>
|
||||||
${msg("Persistent")}
|
${msg("Persistent")}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
|
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
|
||||||
?selected=${this.instance?.nameIdPolicy ===
|
?selected=${this.instance?.nameIdPolicy ===
|
||||||
NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
|
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
|
||||||
>
|
>
|
||||||
${msg("Email address")}
|
${msg("Email address")}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
|
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
|
||||||
?selected=${this.instance?.nameIdPolicy ===
|
?selected=${this.instance?.nameIdPolicy ===
|
||||||
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
|
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
|
||||||
>
|
>
|
||||||
${msg("Windows")}
|
${msg("Windows")}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
|
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
|
||||||
?selected=${this.instance?.nameIdPolicy ===
|
?selected=${this.instance?.nameIdPolicy ===
|
||||||
NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
|
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
|
||||||
>
|
>
|
||||||
${msg("X509 Subject")}
|
${msg("X509 Subject")}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
|
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
|
||||||
?selected=${this.instance?.nameIdPolicy ===
|
?selected=${this.instance?.nameIdPolicy ===
|
||||||
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
|
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
|
||||||
>
|
>
|
||||||
${msg("Transient")}
|
${msg("Transient")}
|
||||||
</option>
|
</option>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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";
|
||||||
@ -48,14 +49,15 @@ 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">
|
|
||||||
<input
|
<ak-slug-input
|
||||||
type="text"
|
name="slug"
|
||||||
value="${ifDefined(this.instance?.slug)}"
|
value=${ifDefined(this.instance?.slug)}
|
||||||
class="pf-c-form-control"
|
label=${msg("Slug")}
|
||||||
required
|
required
|
||||||
/>
|
input-hint="code"
|
||||||
</ak-form-element-horizontal>
|
></ak-slug-input>
|
||||||
|
|
||||||
<ak-form-element-horizontal name="enabled">
|
<ak-form-element-horizontal name="enabled">
|
||||||
<div class="pf-c-check">
|
<div class="pf-c-check">
|
||||||
<input
|
<input
|
||||||
|
@ -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-private-text-input.js";
|
import "@goauthentik/components/ak-secret-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-private-text-input
|
<ak-secret-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-private-text-input>
|
></ak-secret-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-private-text-input
|
<ak-secret-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-private-text-input>
|
></ak-secret-text-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group expanded>
|
||||||
|
@ -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-private-text-input.js";
|
import "@goauthentik/components/ak-secret-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-private-text-input
|
<ak-secret-text-input
|
||||||
name="password"
|
name="password"
|
||||||
label=${msg("SMTP Password")}
|
label=${msg("SMTP Password")}
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-text-input>
|
||||||
|
|
||||||
<ak-form-element-horizontal name="useTls">
|
<ak-form-element-horizontal name="useTls">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
|
@ -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-private-text-input.js";
|
import "@goauthentik/components/ak-secret-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-private-text-input
|
<ak-secret-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-private-text-input>
|
></ak-secret-text-input>
|
||||||
|
|
||||||
<ak-switch-input
|
<ak-switch-input
|
||||||
name="interactive"
|
name="interactive"
|
||||||
|
@ -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-private-text-input.js";
|
import "@goauthentik/components/ak-secret-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-private-text-input
|
<ak-secret-text-input
|
||||||
label=${msg("SMTP Password")}
|
label=${msg("SMTP Password")}
|
||||||
name="password"
|
name="password"
|
||||||
?revealed=${this.instance === undefined}
|
?revealed=${this.instance === undefined}
|
||||||
></ak-private-text-input>
|
></ak-secret-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
|
||||||
|
@ -41,14 +41,27 @@ export class InvitationForm extends ModelForm<Invitation, string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
renderForm(): TemplateResult {
|
||||||
return html` <ak-form-element-horizontal slugMode label=${msg("Name")} required name="name">
|
const checkSlug = (ev: InputEvent) => {
|
||||||
|
if (ev && ev.target && ev.target instanceof HTMLInputElement) {
|
||||||
|
ev.target.value = (ev.target.value ?? "").replace(/[^a-z0-9-]/g, "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return html` <ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||||
<input
|
<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
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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";
|
||||||
@ -124,19 +125,14 @@ 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-form-element-horizontal label=${msg("Password")}>
|
<ak-hidden-text-input
|
||||||
<input
|
label=${msg("Password")}
|
||||||
type="text"
|
value="${this.result?.token ?? ""}"
|
||||||
readonly
|
.help=${msg(
|
||||||
value=${ifDefined(this.result?.token)}
|
|
||||||
class="pf-c-form-control"
|
|
||||||
/>
|
|
||||||
<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.",
|
"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>
|
</ak-hidden-text-input>
|
||||||
</form>`;
|
</form>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,5 @@
|
|||||||
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;
|
||||||
@ -25,121 +9,3 @@ 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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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";
|
||||||
@ -96,13 +97,12 @@ 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;
|
const settings = user.settings as UIConfig;
|
||||||
let config = new DefaultUIConfig();
|
const config = new DefaultUIConfig();
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
config = Object.assign(new DefaultUIConfig(), settings);
|
return deepmerge({ ...config }, settings);
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uiConfig(): Promise<UIConfig> {
|
export function uiConfig(): Promise<UIConfig> {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement, type AKElementProps } 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,6 +6,19 @@ 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
|
||||||
@ -18,37 +31,81 @@ 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 = "";
|
||||||
|
|
||||||
renderControl() {
|
protected renderControl() {
|
||||||
throw new Error("Must be implemented in a subclass");
|
throw new Error("Must be implemented in a subclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
159
web/src/components/ak-hidden-text-input.ts
Normal file
159
web/src/components/ak-hidden-text-input.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
128
web/src/components/ak-hidden-textarea-input.ts
Normal file
128
web/src/components/ak-hidden-textarea-input.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,8 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
|||||||
|
|
||||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
|
import { HorizontalLightComponent } from "./HorizontalLightComponent";
|
||||||
|
|
||||||
@customElement("ak-private-text-input")
|
@customElement("ak-secret-text-input")
|
||||||
export class AkPrivateTextInput extends HorizontalLightComponent<string> {
|
export class AkSecretTextInput extends HorizontalLightComponent<string> {
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
public value = "";
|
public value = "";
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ export class AkPrivateTextInput extends HorizontalLightComponent<string> {
|
|||||||
this.revealed = true;
|
this.revealed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#renderPrivateInput() {
|
#renderSecretInput() {
|
||||||
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 AkPrivateTextInput extends HorizontalLightComponent<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override renderControl() {
|
public override renderControl() {
|
||||||
return this.revealed ? this.renderVisibleInput() : this.#renderPrivateInput();
|
return this.revealed ? this.renderVisibleInput() : this.#renderSecretInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AkPrivateTextInput;
|
export default AkSecretTextInput;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ak-private-text-input": AkPrivateTextInput;
|
"ak-secret-text-input": AkSecretTextInput;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 { AkPrivateTextInput } from "./ak-private-text-input.js";
|
import { AkSecretTextInput } from "./ak-secret-text-input.js";
|
||||||
|
|
||||||
@customElement("ak-private-textarea-input")
|
@customElement("ak-secret-textarea-input")
|
||||||
export class AkPrivateTextAreaInput extends AkPrivateTextInput {
|
export class AkSecretTextAreaInput extends AkSecretTextInput {
|
||||||
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 AkPrivateTextAreaInput extends AkPrivateTextInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AkPrivateTextAreaInput;
|
export default AkSecretTextAreaInput;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ak-private-textarea-input": AkPrivateTextAreaInput;
|
"ak-secret-textarea-input": AkSecretTextAreaInput;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { formatSlug } from "@goauthentik/elements/router/utils.js";
|
import { bound } from "@goauthentik/elements/decorators/bound.js";
|
||||||
|
import { kebabCase } from "change-case";
|
||||||
|
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators.js";
|
import { customElement, property, query } from "lit/decorators.js";
|
||||||
@ -6,59 +7,83 @@ 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> {
|
||||||
@property({ type: String, reflect: true })
|
/**
|
||||||
value = "";
|
* A selector indicating the source text input control. Must be unique within the whole DOM
|
||||||
|
* context of the slug and source controls. The most common use in authentik is the default:
|
||||||
|
* slugifying the "name" of something.
|
||||||
|
*/
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
source = "";
|
public source = "[name='name']";
|
||||||
|
|
||||||
origin?: HTMLInputElement | null;
|
@property({ type: String, reflect: true })
|
||||||
|
public value = "";
|
||||||
|
|
||||||
@query("input")
|
@query("input")
|
||||||
input!: HTMLInputElement;
|
private input!: HTMLInputElement;
|
||||||
|
|
||||||
touched: boolean = false;
|
#origin?: HTMLInputElement | null;
|
||||||
|
|
||||||
constructor() {
|
#touched: boolean = false;
|
||||||
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.
|
||||||
handleTouch(ev: Event) {
|
protected handleTouch(ev: Event) {
|
||||||
this.input.value = formatSlug(this.input.value);
|
this.value = this.input.value = slugify(this.input.value);
|
||||||
this.value = this.input.value;
|
|
||||||
|
|
||||||
if (this.origin && this.origin.value === "" && this.input.value === "") {
|
// Reset 'touched' status if the slug & target have been reset
|
||||||
this.touched = false;
|
if (this.#origin && this.#origin.value === "" && this.input.value === "") {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slugify(ev: Event) {
|
@bound
|
||||||
|
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
|
// Don't proceed if the user has hand-modified the slug. (Note the order of statements: if
|
||||||
if (this.touched) {
|
// the user hand modified the slug to be empty as part of resetting the slug/source
|
||||||
|
// relationship, that's a "not-touched" condition and falls through.)
|
||||||
|
if (this.#touched) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +92,7 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
|
|||||||
// "any event which adds or removes a character but leaves the rest of the slug looking like
|
// "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 = formatSlug(ev.target.value);
|
const newSlug = slugify(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];
|
||||||
@ -81,7 +106,6 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
|
|||||||
// to listeners, both the name and value of the host must match those of the target
|
// 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", {
|
||||||
@ -91,38 +115,36 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
public override disconnectedCallback() {
|
||||||
super.connectedCallback();
|
if (this.#origin) {
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderControl() {
|
public override 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;
|
||||||
|
89
web/src/components/ak-visibility-toggle.ts
Normal file
89
web/src/components/ak-visibility-toggle.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -94,7 +94,8 @@ export class ObjectChangelog extends Table<Event> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
@ -66,7 +66,8 @@ export class UserEvents extends Table<Event> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
93
web/src/components/stories/ak-hidden-text-input.stories.ts
Normal file
93
web/src/components/stories/ak-hidden-text-input.stories.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
140
web/src/components/stories/ak-hidden-textarea-input.stories.ts
Normal file
140
web/src/components/stories/ak-hidden-textarea-input.stories.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
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",
|
||||||
|
},
|
||||||
|
};
|
121
web/src/components/stories/ak-visibility-toggle.stories.ts
Normal file
121
web/src/components/stories/ak-visibility-toggle.stories.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
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>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
};
|
@ -16,8 +16,12 @@ 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 {
|
export class AKElement extends LitElement implements AKElementProps {
|
||||||
//#region Static Properties
|
//#region Static Properties
|
||||||
|
|
||||||
public static styles?: Array<CSSResult | CSSModule>;
|
public static styles?: Array<CSSResult | CSSModule>;
|
||||||
|
@ -3,6 +3,7 @@ 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";
|
||||||
@ -33,6 +34,8 @@ 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,
|
||||||
@ -48,6 +51,12 @@ 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
|
||||||
@ -59,15 +68,17 @@ 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>`}
|
||||||
<h1 class="pf-c-title pf-m-lg">
|
${showHeader ? html` <h1 class="pf-c-title pf-m-lg">${header()}</h1>` : nothing}
|
||||||
${this.loading && this.header === undefined ? msg("Loading") : this.header}
|
${this.slots.hasSlotted("body")
|
||||||
</h1>
|
? html` <div class="pf-c-empty-state__body">
|
||||||
<div class="pf-c-empty-state__body">
|
|
||||||
<slot name="body"></slot>
|
<slot name="body"></slot>
|
||||||
</div>
|
</div>`
|
||||||
<div class="pf-c-empty-state__primary">
|
: nothing}
|
||||||
|
${this.slots.hasSlotted("primary")
|
||||||
|
? html` <div class="pf-c-empty-state__primary">
|
||||||
<slot name="primary"></slot>
|
<slot name="primary"></slot>
|
||||||
</div>
|
</div>`
|
||||||
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
@ -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] || a[0];
|
const bSortBy = b[2] || b[0];
|
||||||
|
|
||||||
return aSortBy.localeCompare(bSortBy);
|
return aSortBy.localeCompare(bSortBy);
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,8 @@ export abstract class AKChart<T> extends AKElement {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
${this.error
|
${this.error
|
||||||
? html`
|
? html`
|
||||||
<ak-empty-state header="${msg("Failed to fetch data.")}" icon="fa-times">
|
<ak-empty-state icon="fa-times"
|
||||||
|
><span slot="header">${msg("Failed to fetch data.")}</span>
|
||||||
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
`
|
`
|
||||||
|
@ -40,7 +40,9 @@ export class LogViewer extends Table<LogEvent> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`,
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No log messages.")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import { AKElement } from "@goauthentik/elements/Base";
|
|||||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
import { 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";
|
||||||
@ -197,39 +196,6 @@ 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();
|
||||||
|
@ -77,9 +77,6 @@ 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
|
||||||
@ -109,13 +106,6 @@ 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);
|
||||||
|
@ -163,7 +163,8 @@ export class NotificationDrawer extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderEmpty() {
|
renderEmpty() {
|
||||||
return html`<ak-empty-state header=${msg("No notifications found.")}>
|
return html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No notifications found.")}</span>
|
||||||
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
@ -288,7 +288,9 @@ 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 header=${msg("Loading")}></ak-empty-state>
|
<ak-empty-state loading
|
||||||
|
><span slot="header">${msg("Loading")}</span></ak-empty-state
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
@ -300,8 +302,9 @@ 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 header="${msg("No objects found.")}"
|
html`<ak-empty-state
|
||||||
><div slot="primary">${this.renderObjectCreate()}</div>
|
><span slot="header">${msg("No objects found.")}</span> >
|
||||||
|
<div slot="primary">${this.renderObjectCreate()}</div>
|
||||||
</ak-empty-state>`}
|
</ak-empty-state>`}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -316,7 +319,8 @@ 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 header="${msg("Failed to fetch objects.")}" icon="fa-ban">
|
return html`<ak-empty-state icon="fa-ban"
|
||||||
|
><span slot="header">${msg("Failed to fetch objects.")}</span>
|
||||||
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,11 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render the default loader", async () => {
|
it("should render the default loader", async () => {
|
||||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
render(
|
||||||
|
html`<ak-empty-state loading
|
||||||
|
><span slot="header">${msg("Loading")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
|
);
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -29,7 +33,11 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should handle standard boolean", async () => {
|
it("should handle standard boolean", async () => {
|
||||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
render(
|
||||||
|
html`<ak-empty-state loading
|
||||||
|
><span slot="header">${msg("Loading")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
|
);
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -39,7 +47,11 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render a static empty state", async () => {
|
it("should render a static empty state", async () => {
|
||||||
render(html`<ak-empty-state header=${msg("No messages found")}> </ak-empty-state>`);
|
render(
|
||||||
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No messages found")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
|
);
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -51,7 +63,8 @@ describe("ak-empty-state", () => {
|
|||||||
|
|
||||||
it("should render a slotted message", async () => {
|
it("should render a slotted message", async () => {
|
||||||
render(
|
render(
|
||||||
html`<ak-empty-state header=${msg("No messages found")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No messages found")}</span>
|
||||||
<p slot="body">Try again with a different filter</p>
|
<p slot="body">Try again with a different filter</p>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { checkWebAuthnSupport } from "@goauthentik/common/helpers/webauthn";
|
||||||
checkWebAuthnSupport,
|
|
||||||
transformAssertionForServer,
|
|
||||||
transformCredentialRequestOptions,
|
|
||||||
} from "@goauthentik/common/helpers/webauthn";
|
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
import { BaseDeviceStage } from "@goauthentik/flow/stages/authenticator_validate/base";
|
import { BaseDeviceStage } from "@goauthentik/flow/stages/authenticator_validate/base";
|
||||||
|
|
||||||
@ -38,12 +34,12 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage<
|
|||||||
async authenticate(): Promise<void> {
|
async authenticate(): Promise<void> {
|
||||||
// request the authenticator to create an assertion signature using the
|
// request the authenticator to create an assertion signature using the
|
||||||
// credential private key
|
// credential private key
|
||||||
let assertion;
|
let assertion: PublicKeyCredential;
|
||||||
checkWebAuthnSupport();
|
checkWebAuthnSupport();
|
||||||
try {
|
try {
|
||||||
assertion = await navigator.credentials.get({
|
assertion = (await navigator.credentials.get({
|
||||||
publicKey: this.transformedCredentialRequestOptions,
|
publicKey: this.transformedCredentialRequestOptions,
|
||||||
});
|
})) as PublicKeyCredential;
|
||||||
if (!assertion) {
|
if (!assertion) {
|
||||||
throw new Error("Assertions is empty");
|
throw new Error("Assertions is empty");
|
||||||
}
|
}
|
||||||
@ -51,17 +47,11 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage<
|
|||||||
throw new Error(`Error when creating credential: ${err}`);
|
throw new Error(`Error when creating credential: ${err}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we now have an authentication assertion! encode the byte arrays contained
|
|
||||||
// in the assertion data as strings for posting to the server
|
|
||||||
const transformedAssertionForServer = transformAssertionForServer(
|
|
||||||
assertion as PublicKeyCredential,
|
|
||||||
);
|
|
||||||
|
|
||||||
// post the assertion to the server for verification.
|
// post the assertion to the server for verification.
|
||||||
try {
|
try {
|
||||||
await this.host?.submit(
|
await this.host?.submit(
|
||||||
{
|
{
|
||||||
webauthn: transformedAssertionForServer,
|
webauthn: assertion.toJSON(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
invisible: true,
|
invisible: true,
|
||||||
@ -74,12 +64,10 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage<
|
|||||||
|
|
||||||
updated(changedProperties: PropertyValues<this>) {
|
updated(changedProperties: PropertyValues<this>) {
|
||||||
if (changedProperties.has("challenge") && this.challenge !== undefined) {
|
if (changedProperties.has("challenge") && this.challenge !== undefined) {
|
||||||
// convert certain members of the PublicKeyCredentialRequestOptions into
|
|
||||||
// byte arrays as expected by the spec.
|
|
||||||
const credentialRequestOptions = this.deviceChallenge
|
const credentialRequestOptions = this.deviceChallenge
|
||||||
?.challenge as PublicKeyCredentialRequestOptions;
|
?.challenge as unknown as PublicKeyCredentialRequestOptionsJSON;
|
||||||
this.transformedCredentialRequestOptions =
|
this.transformedCredentialRequestOptions =
|
||||||
transformCredentialRequestOptions(credentialRequestOptions);
|
PublicKeyCredential.parseRequestOptionsFromJSON(credentialRequestOptions);
|
||||||
this.authenticateWrapper();
|
this.authenticateWrapper();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { checkWebAuthnSupport } from "@goauthentik/common/helpers/webauthn";
|
||||||
Assertion,
|
|
||||||
checkWebAuthnSupport,
|
|
||||||
transformCredentialCreateOptions,
|
|
||||||
transformNewAssertionForServer,
|
|
||||||
} from "@goauthentik/common/helpers/webauthn";
|
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||||
|
|
||||||
@ -24,10 +19,6 @@ import {
|
|||||||
AuthenticatorWebAuthnChallengeResponseRequest,
|
AuthenticatorWebAuthnChallengeResponseRequest,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
export interface WebAuthnAuthenticatorRegisterChallengeResponse {
|
|
||||||
response: Assertion;
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ak-stage-authenticator-webauthn")
|
@customElement("ak-stage-authenticator-webauthn")
|
||||||
export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
||||||
AuthenticatorWebAuthnChallenge,
|
AuthenticatorWebAuthnChallenge,
|
||||||
@ -68,7 +59,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
|||||||
}
|
}
|
||||||
checkWebAuthnSupport();
|
checkWebAuthnSupport();
|
||||||
// request the authenticator(s) to create a new credential keypair.
|
// request the authenticator(s) to create a new credential keypair.
|
||||||
let credential;
|
let credential: PublicKeyCredential;
|
||||||
try {
|
try {
|
||||||
credential = (await navigator.credentials.create({
|
credential = (await navigator.credentials.create({
|
||||||
publicKey: this.publicKeyCredentialCreateOptions,
|
publicKey: this.publicKeyCredentialCreateOptions,
|
||||||
@ -80,16 +71,12 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
|||||||
throw new Error(msg(str`Error creating credential: ${err}`));
|
throw new Error(msg(str`Error creating credential: ${err}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// we now have a new credential! We now need to encode the byte arrays
|
|
||||||
// in the credential into strings, for posting to our server.
|
|
||||||
const newAssertionForServer = transformNewAssertionForServer(credential);
|
|
||||||
|
|
||||||
// post the transformed credential data to the server for validation
|
// post the transformed credential data to the server for validation
|
||||||
// and storing the public key
|
// and storing the public key
|
||||||
try {
|
try {
|
||||||
await this.host?.submit(
|
await this.host?.submit(
|
||||||
{
|
{
|
||||||
response: newAssertionForServer,
|
response: credential.toJSON(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
invisible: true,
|
invisible: true,
|
||||||
@ -118,11 +105,9 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
|||||||
|
|
||||||
updated(changedProperties: PropertyValues<this>) {
|
updated(changedProperties: PropertyValues<this>) {
|
||||||
if (changedProperties.has("challenge") && this.challenge !== undefined) {
|
if (changedProperties.has("challenge") && this.challenge !== undefined) {
|
||||||
// convert certain members of the PublicKeyCredentialCreateOptions into
|
this.publicKeyCredentialCreateOptions =
|
||||||
// byte arrays as expected by the spec.
|
PublicKeyCredential.parseCreationOptionsFromJSON(
|
||||||
this.publicKeyCredentialCreateOptions = transformCredentialCreateOptions(
|
this.challenge?.registration as PublicKeyCredentialCreationOptionsJSON,
|
||||||
this.challenge?.registration as PublicKeyCredentialCreationOptions,
|
|
||||||
this.challenge?.registration.user.id,
|
|
||||||
);
|
);
|
||||||
this.registerWrapper();
|
this.registerWrapper();
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import "construct-style-sheets-polyfill";
|
|||||||
import "@webcomponents/webcomponentsjs";
|
import "@webcomponents/webcomponentsjs";
|
||||||
import "lit/polyfill-support.js";
|
import "lit/polyfill-support.js";
|
||||||
import "core-js/actual";
|
import "core-js/actual";
|
||||||
|
import "webauthn-polyfills";
|
||||||
|
|
||||||
import "@formatjs/intl-listformat/polyfill";
|
import "@formatjs/intl-listformat/polyfill";
|
||||||
import "@formatjs/intl-listformat/locale-data/en";
|
import "@formatjs/intl-listformat/locale-data/en";
|
||||||
|
2
web/types/node.d.ts
vendored
2
web/types/node.d.ts
vendored
@ -14,7 +14,7 @@ declare module "module" {
|
|||||||
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var __dirname: string;
|
var __dirname: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3001,11 +3001,6 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||||||
<source>Load servers</source>
|
<source>Load servers</source>
|
||||||
<target>Server laden</target>
|
<target>Server laden</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s24f405197ede5ebb">
|
|
||||||
<source>Re-authenticate with plex</source>
|
|
||||||
<target>Mit Plex erneut authentifizieren</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc297b2e13c28ecf9">
|
<trans-unit id="sc297b2e13c28ecf9">
|
||||||
<source>Allow friends to authenticate via Plex, even if you don't share any servers</source>
|
<source>Allow friends to authenticate via Plex, even if you don't share any servers</source>
|
||||||
@ -9241,6 +9236,18 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sb3d5c0a0501669df">
|
<trans-unit id="sb3d5c0a0501669df">
|
||||||
<source>Generate New Certificate-Key Pair</source>
|
<source>Generate New Certificate-Key Pair</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf9686d31d28fcf7d">
|
||||||
|
<source>Show field content</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb1b05a7573ab618c">
|
||||||
|
<source>Hide field content</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s4f820625804ed29b">
|
||||||
|
<source>Re-authenticate with Plex</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0433d667ea6eec1a">
|
||||||
|
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -2414,10 +2414,6 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||||||
<source>Load servers</source>
|
<source>Load servers</source>
|
||||||
<target>Load servers</target>
|
<target>Load servers</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s24f405197ede5ebb">
|
|
||||||
<source>Re-authenticate with plex</source>
|
|
||||||
<target>Re-authenticate with plex</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sc297b2e13c28ecf9">
|
<trans-unit id="sc297b2e13c28ecf9">
|
||||||
<source>Allow friends to authenticate via Plex, even if you don't share any servers</source>
|
<source>Allow friends to authenticate via Plex, even if you don't share any servers</source>
|
||||||
<target>Allow friends to authenticate via Plex, even if you don't share any servers</target>
|
<target>Allow friends to authenticate via Plex, even if you don't share any servers</target>
|
||||||
@ -7748,6 +7744,18 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sb3d5c0a0501669df">
|
<trans-unit id="sb3d5c0a0501669df">
|
||||||
<source>Generate New Certificate-Key Pair</source>
|
<source>Generate New Certificate-Key Pair</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf9686d31d28fcf7d">
|
||||||
|
<source>Show field content</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb1b05a7573ab618c">
|
||||||
|
<source>Hide field content</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s4f820625804ed29b">
|
||||||
|
<source>Re-authenticate with Plex</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0433d667ea6eec1a">
|
||||||
|
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -2982,11 +2982,6 @@ no se aprueba cuando una o ambas de las opciones seleccionadas son iguales o sup
|
|||||||
<source>Load servers</source>
|
<source>Load servers</source>
|
||||||
<target>Servidores de carga</target>
|
<target>Servidores de carga</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s24f405197ede5ebb">
|
|
||||||
<source>Re-authenticate with plex</source>
|
|
||||||
<target>Vuelva a autenticarse con plex</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc297b2e13c28ecf9">
|
<trans-unit id="sc297b2e13c28ecf9">
|
||||||
<source>Allow friends to authenticate via Plex, even if you don't share any servers</source>
|
<source>Allow friends to authenticate via Plex, even if you don't share any servers</source>
|
||||||
@ -9301,6 +9296,18 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sb3d5c0a0501669df">
|
<trans-unit id="sb3d5c0a0501669df">
|
||||||
<source>Generate New Certificate-Key Pair</source>
|
<source>Generate New Certificate-Key Pair</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf9686d31d28fcf7d">
|
||||||
|
<source>Show field content</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb1b05a7573ab618c">
|
||||||
|
<source>Hide field content</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s4f820625804ed29b">
|
||||||
|
<source>Re-authenticate with Plex</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0433d667ea6eec1a">
|
||||||
|
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user