Merge remote-tracking branch 'origin/main' into dominic/resolve-files

# Conflicts:
#	web/src/flow/FlowExecutor.ts
This commit is contained in:
Dominic R
2026-08-27 11:05:14 -04:00
660 changed files with 3514 additions and 1888 deletions

View File

@@ -64,7 +64,7 @@ runs:
rustflags: ""
- name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2
uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup pnpm

View File

@@ -62,7 +62,7 @@ jobs:
- build-amd64
- build-arm64
steps:
- uses: regclient/actions/regctl-installer@78eb729dbdb4ef6480e85ff697b4410e22112583
- uses: regclient/actions/regctl-installer@f9ceff9bbbc63cd1008e60cec2b27627eedc7322
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v5
with:
artifact-ids: "${{ needs.build-amd64.outputs.artifact-id }},${{ needs.build-arm64.outputs.artifact-id }}"

View File

@@ -50,6 +50,6 @@ jobs:
- check-changes-applied
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
- uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # release/v1
with:
jobs: ${{ toJSON(needs) }}

View File

@@ -143,6 +143,6 @@ jobs:
- build-container
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
- uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # release/v1
with:
jobs: ${{ toJSON(needs) }}

View File

@@ -479,7 +479,7 @@ jobs:
- test-rust
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
- uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # release/v1
with:
jobs: ${{ toJSON(needs) }}
publish:
@@ -508,9 +508,9 @@ jobs:
- radius
- rac
steps:
- uses: regclient/actions/regctl-installer@78eb729dbdb4ef6480e85ff697b4410e22112583
- uses: regclient/actions/regctl-installer@f9ceff9bbbc63cd1008e60cec2b27627eedc7322
# logs in to ghcr.io by default
- uses: regclient/actions/regctl-login@78eb729dbdb4ef6480e85ff697b4410e22112583
- uses: regclient/actions/regctl-login@f9ceff9bbbc63cd1008e60cec2b27627eedc7322
# Docker login is required for attestations
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with:

View File

@@ -38,11 +38,6 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v8
with:
# latest (v2.13.0) bundles honnef.co/go/tools v0.8.0-rc.1, whose nilness
# analyzer panics on getsentry/sentry-go ("unhandled builtin recover",
# https://github.com/dominikh/go-tools/issues/1725). Unpin once a
# release with the fix ships.
version: v2.12.2
args: --timeout 5000s --verbose
skip-cache: true
test-unittest:
@@ -67,7 +62,7 @@ jobs:
- test-unittest
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
- uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # release/v1
with:
jobs: ${{ toJSON(needs) }}
build-binary:

View File

@@ -80,7 +80,7 @@ jobs:
- lint
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
- uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # release/v1
with:
jobs: ${{ toJSON(needs) }}
test:

View File

@@ -52,10 +52,10 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5
- uses: regclient/actions/regctl-installer@78eb729dbdb4ef6480e85ff697b4410e22112583
- uses: regclient/actions/regctl-installer@f9ceff9bbbc63cd1008e60cec2b27627eedc7322
# logs in to ghcr.io by default
- uses: regclient/actions/regctl-login@78eb729dbdb4ef6480e85ff697b4410e22112583
- uses: regclient/actions/regctl-login@78eb729dbdb4ef6480e85ff697b4410e22112583
- uses: regclient/actions/regctl-login@f9ceff9bbbc63cd1008e60cec2b27627eedc7322
- uses: regclient/actions/regctl-login@f9ceff9bbbc63cd1008e60cec2b27627eedc7322
with:
registry: docker.io
username: "${{ secrets.DOCKER_CORP_USERNAME }}"

View File

@@ -144,9 +144,9 @@ Authoritative contributor docs live under `website/docs/developer-docs/` and are
- `setup/full-dev-environment.mdx` — full backend + frontend dev environment.
- `setup/frontend-dev-environment.mdx` — web-only setup.
- `setup/debugging.md` — attaching a debugger (VS Code config included).
- `setup/debugging.mdx` — attaching a debugger (VS Code config included).
- `docs/style-guide.mdx` — the canonical prose style guide (also governs this repo's docs).
- `contributing.md` / top-level `CONTRIBUTING.md` — contribution process. `SECURITY.md` — reporting vulnerabilities.
- `contributing.mdx` / top-level `CONTRIBUTING.md` — contribution process. `SECURITY.md` — reporting vulnerabilities.
## Tech stack

View File

@@ -53,7 +53,7 @@ from authentik.policies.models import Policy, PolicyBindingModel
from authentik.rbac.models import Role
# Context set when the serializer is created in a blueprint context
# Update website/docs/customize/blueprints/v1/models.md when used
# Update website/docs/customize/blueprints/v1/models.mdx when used
SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry"

View File

@@ -9,6 +9,7 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509.base import load_pem_x509_certificate
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django_dramatiq_postgres.models import TaskState
from dramatiq.actor import actor
from dramatiq.middleware import Middleware
from structlog.stdlib import get_logger
@@ -23,6 +24,7 @@ from watchdog.observers import Observer
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.config import CONFIG
from authentik.tasks.middleware import CurrentTask
from authentik.tasks.models import Task
from authentik.tasks.schedules.models import Schedule
from authentik.tenants.models import Tenant
@@ -85,15 +87,24 @@ class CertificateEventHandler(FileSystemEventHandler):
return None
return super().dispatch(event)
def run_tasks(self):
for tenant in Tenant.objects.filter(ready=True):
with tenant:
if Task.objects.filter(
tenant=tenant,
actor_name=certificate_discovery.actor_name,
state=TaskState.QUEUED,
).exists():
continue
Schedule.dispatch_by_actor(certificate_discovery)
def on_created(self, event: FileSystemEvent):
"""Process certificate file creation"""
LOGGER.debug(
"Certificate file created, triggering discovery",
file=event.src_path,
)
for tenant in Tenant.objects.filter(ready=True):
with tenant:
Schedule.dispatch_by_actor(certificate_discovery)
self.run_tasks()
def on_modified(self, event: FileSystemEvent):
"""Process certificate file modification"""
@@ -101,9 +112,7 @@ class CertificateEventHandler(FileSystemEventHandler):
"Certificate file modified, triggering discovery",
file=event.src_path,
)
for tenant in Tenant.objects.filter(ready=True):
with tenant:
Schedule.dispatch_by_actor(certificate_discovery)
self.run_tasks()
@actor(description=_("Discover, import and update certificates from the filesystem."))

View File

@@ -2,6 +2,7 @@
import re
import socket
from copy import deepcopy
from ipaddress import ip_address, ip_network
from smtplib import SMTPException
from textwrap import indent
@@ -62,8 +63,8 @@ class BaseEvaluator:
def __init__(self, filename: str | None = None):
self._filename = filename if filename else "BaseEvaluator"
# update website/docs/expressions/_objects.md
# update website/docs/expressions/_functions.md
# update website/docs/expressions/reference/_objects.mdx
# update website/docs/expressions/reference/_functions.mdx
self._globals = {
"ak_call_policy": self.expr_func_call_policy,
"ak_create_event": self.expr_event_create,
@@ -174,8 +175,8 @@ class BaseEvaluator:
return fallback value."""
attrs = getattr(obj, "attributes", {})
value = get_path_from_dict(attrs, attr_key)
if value is None and fallback:
return getattr(obj, fallback)
if value is None and fallback is not None:
return getattr(obj, fallback, fallback)
return value
def expr_event_create(self, action: str, **kwargs):
@@ -207,7 +208,7 @@ class BaseEvaluator:
user = self._context.get("user", get_anonymous_user())
req = PolicyRequest(user)
if "request" in self._context:
req = self._context["request"]
req = deepcopy(self._context["request"])
req.context.update(kwargs)
proc = PolicyProcess(PolicyBinding(policy=policy), request=req, connection=None)
return proc.profiling_wrapper()

View File

@@ -39,6 +39,17 @@ class TestEvaluator(TestCase):
"""Test expr_is_group_member"""
self.assertFalse(BaseEvaluator.expr_is_group_member(create_test_admin_user(), name="test"))
def test_expr_obj_attr(self):
"""Test expr_obj_attr"""
user = create_test_user()
user.attributes = {"locale": "en-US"}
self.assertEqual(BaseEvaluator.expr_obj_attr(user, "locale", "en-GB"), "en-US")
self.assertEqual(BaseEvaluator.expr_obj_attr(user, "missing", "username"), user.username)
self.assertEqual(BaseEvaluator.expr_obj_attr(user, "missing", "en-GB"), "en-GB")
self.assertEqual(BaseEvaluator.expr_obj_attr(user, "missing", ""), "")
self.assertIsNone(BaseEvaluator.expr_obj_attr(user, "missing"))
def test_expr_event_create(self):
"""Test expr_event_create"""
evaluator = BaseEvaluator(generate_id())

View File

@@ -53,7 +53,7 @@ class ServiceConnectionInvalid(SentryIgnoredException):
class OutpostConfig:
"""Configuration an outpost uses to configure it self"""
# update website/docs/add-secure-apps/outposts/_config.md
# update website/docs/add-secure-apps/outposts/_config.mdx
authentik_host: str = ""
authentik_host_insecure: bool = False

View File

@@ -28,8 +28,8 @@ class PolicyEvaluator(BaseEvaluator):
def __init__(self, policy_name: str | None = None):
super().__init__(policy_name or "PolicyEvaluator")
self._messages = []
# update website/docs/expressions/_objects.md
# update website/docs/expressions/_functions.md
# update website/docs/expressions/reference/_objects.mdx
# update website/docs/expressions/reference/_functions.mdx
self._context["ak_message"] = self.expr_func_message
self._context["ak_user_has_authenticator"] = self.expr_func_user_has_authenticator
@@ -39,8 +39,8 @@ class PolicyEvaluator(BaseEvaluator):
def set_policy_request(self, request: PolicyRequest):
"""Update context based on policy request (if http request is given, update that too)"""
# update website/docs/expressions/_objects.md
# update website/docs/expressions/_functions.md
# update website/docs/expressions/reference/_objects.mdx
# update website/docs/expressions/reference/_functions.mdx
self._context["ak_is_sso_flow"] = request.context.get(PLAN_CONTEXT_SSO, False)
if request.http_request:
self.set_http_request(request.http_request)
@@ -53,8 +53,8 @@ class PolicyEvaluator(BaseEvaluator):
def set_http_request(self, request: HttpRequest):
"""Update context based on http request"""
# update website/docs/expressions/_objects.md
# update website/docs/expressions/_functions.md
# update website/docs/expressions/reference/_objects.mdx
# update website/docs/expressions/reference/_functions.mdx
self._context["ak_client_ip"] = ip_address(ClientIPMiddleware.get_client_ip(request))
self._context["http_request"] = request

View File

@@ -111,6 +111,25 @@ class TestEvaluator(TestCase):
res = proc.profiling_wrapper()
self.assertEqual(res.messages, ("/", "/", "/"))
def test_call_policy_kwargs_pollute(self):
"""test ak_call_policy"""
expr = ExpressionPolicy.objects.create(
name=generate_id(),
execution_logging=True,
expression="return context.get('subkey', False)",
)
expr2 = ExpressionPolicy.objects.create(
name=generate_id(),
execution_logging=True,
expression=f"""
ak_message(ak_call_policy('{expr.name}', subkey=True).passing)
ak_message(ak_call_policy('{expr.name}').passing)
""",
)
proc = PolicyProcess(PolicyBinding(policy=expr2), request=self.request, connection=None)
res = proc.profiling_wrapper()
self.assertEqual(res.messages, (True, False))
def test_call_policy_test_like(self):
"""test ak_call_policy without `obj` set, as if it was when testing policies"""
expr = ExpressionPolicy.objects.create(

View File

@@ -1,8 +1,10 @@
"""AuthenticatorDuoStage API Views"""
from ssl import SSLCertVerificationError, SSLError
from typing import Any
from django.http import Http404
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
from guardian.shortcuts import get_objects_for_user
@@ -208,10 +210,33 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
)
created += 1
return {"error": "", "count": created}
# `duo_client` surfaces transport failures as the underlying socket/TLS
# error, which is an OSError and not a RuntimeError. Catching only
# RuntimeError let those escape the view entirely, so an unreachable or
# untrusted Duo endpoint produced no actionable error at all.
# SSLCertVerificationError < SSLError < OSError, so order matters here.
except SSLCertVerificationError as exc:
LOGGER.warning("failed to verify duo api certificate", exc=exc)
return {
"error": _("Failed to connect to Duo: TLS certificate verification failed."),
"count": created,
}
except SSLError as exc:
LOGGER.warning("tls error connecting to duo", exc=exc)
return {
"error": _("Failed to connect to Duo: TLS error."),
"count": created,
}
except OSError as exc:
LOGGER.warning("failed to connect to duo", exc=exc)
return {
"error": _("Failed to connect to Duo."),
"count": created,
}
except RuntimeError as exc:
LOGGER.warning("failed to get users from duo", exc=exc)
return {
"error": "An internal error occurred while importing devices.",
"error": _("An internal error occurred while importing devices."),
"count": created,
}

View File

@@ -1,5 +1,6 @@
"""Test duo stage"""
from ssl import SSLCertVerificationError, SSLError
from unittest.mock import MagicMock, patch
from uuid import uuid4
@@ -187,6 +188,120 @@ class AuthenticatorDuoStageTests(FlowTestCase):
},
)
def test_api_import_automatic_tls_failure(self):
"""test `import_devices_automatic` when the Duo API certificate doesn't verify
Regression test for #22896: `SSLCertVerificationError` is an `OSError`,
not a `RuntimeError`, so it escaped the handler entirely instead of
producing the documented 400 with a descriptive error.
"""
self.client.force_login(self.user)
stage = AuthenticatorDuoStage.objects.create(
name=generate_id(),
client_id=generate_id(),
client_secret=generate_id(),
api_hostname=generate_id(),
admin_integration_key=generate_id(),
admin_secret_key=generate_id(),
)
ssl_error = SSLCertVerificationError(
1,
"[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: "
"unable to get local issuer certificate (_ssl.c:1081)",
)
with patch(
"duo_client.admin.Admin.get_users_iterator",
MagicMock(side_effect=ssl_error),
):
response = self.client.post(
reverse(
"authentik_api:authenticatorduostage-import-devices-automatic",
kwargs={
"pk": str(stage.pk),
},
),
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content,
{
"error": "Failed to connect to Duo: TLS certificate verification failed.",
"count": 0,
},
)
def test_api_import_automatic_tls_error(self):
"""test `import_devices_automatic` on a non-certificate TLS failure
A generic `SSLError` must be reported as a TLS error rather than
claiming certificate verification specifically failed.
"""
self.client.force_login(self.user)
stage = AuthenticatorDuoStage.objects.create(
name=generate_id(),
client_id=generate_id(),
client_secret=generate_id(),
api_hostname=generate_id(),
admin_integration_key=generate_id(),
admin_secret_key=generate_id(),
)
with patch(
"duo_client.admin.Admin.get_users_iterator",
MagicMock(side_effect=SSLError("handshake failure")),
):
response = self.client.post(
reverse(
"authentik_api:authenticatorduostage-import-devices-automatic",
kwargs={
"pk": str(stage.pk),
},
),
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content,
{
"error": "Failed to connect to Duo: TLS error.",
"count": 0,
},
)
def test_api_import_automatic_connection_failure(self):
"""test `import_devices_automatic` when the Duo API is unreachable
A non-TLS `OSError` must also surface as a descriptive 400 rather than
escaping the handler.
"""
self.client.force_login(self.user)
stage = AuthenticatorDuoStage.objects.create(
name=generate_id(),
client_id=generate_id(),
client_secret=generate_id(),
api_hostname=generate_id(),
admin_integration_key=generate_id(),
admin_secret_key=generate_id(),
)
with patch(
"duo_client.admin.Admin.get_users_iterator",
MagicMock(side_effect=OSError("connection refused")),
):
response = self.client.post(
reverse(
"authentik_api:authenticatorduostage-import-devices-automatic",
kwargs={
"pk": str(stage.pk),
},
),
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content,
{
"error": "Failed to connect to Duo.",
"count": 0,
},
)
def test_api_import_automatic(self):
"""test `import_devices_automatic`"""
self.client.force_login(self.user)

View File

@@ -45,6 +45,7 @@ from authentik.stages.authenticator_validate.challenge import (
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.captcha.stage import (
PLAN_CONTEXT_CAPTCHA_PRIVATE_KEY,
PLAN_CONTEXT_CAPTCHA_SITE_KEY,
CaptchaChallenge,
verify_captcha_token,
)
@@ -347,7 +348,10 @@ class IdentificationStageView(ChallengeStageView):
"captcha_stage": (
{
"js_url": current_stage.captcha_stage.js_url,
"site_key": current_stage.captcha_stage.public_key,
"site_key": self.executor.plan.context.get(
PLAN_CONTEXT_CAPTCHA_SITE_KEY,
current_stage.captcha_stage.public_key,
),
"interactive": current_stage.captcha_stage.interactive,
"pending_user": "",
"pending_user_avatar": DEFAULT_AVATAR,

View File

@@ -13,6 +13,10 @@ from authentik.sources.oauth.models import OAuthSource
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.captcha.models import CaptchaStage
from authentik.stages.captcha.stage import (
PLAN_CONTEXT_CAPTCHA_PRIVATE_KEY,
PLAN_CONTEXT_CAPTCHA_SITE_KEY,
)
from authentik.stages.captcha.tests import RECAPTCHA_PRIVATE_KEY, RECAPTCHA_PUBLIC_KEY
from authentik.stages.identification.api import IdentificationStageSerializer
from authentik.stages.identification.models import IdentificationStage, UserFields
@@ -278,6 +282,53 @@ class TestIdentificationStage(FlowTestCase):
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
@Mocker()
def test_valid_with_captcha_override(self, mock: Mocker):
"""Test embedded captcha with overridden site and private keys"""
mock.post(
"https://www.recaptcha.net/recaptcha/api/siteverify",
json={
"success": True,
"score": 0.5,
},
)
configured_site_key = generate_id()
captcha_stage = CaptchaStage.objects.create(
name="captcha",
public_key=configured_site_key,
private_key=generate_id(),
)
self.stage.captcha_stage = captcha_stage
self.stage.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.get(url)
challenge = self.assertStageResponse(response, self.flow)
self.assertEqual(challenge["captcha_stage"]["site_key"], configured_site_key)
site_key = generate_id()
private_key = generate_id()
plan = self.get_flow_plan()
plan.context.update(
{
PLAN_CONTEXT_CAPTCHA_SITE_KEY: site_key,
PLAN_CONTEXT_CAPTCHA_PRIVATE_KEY: private_key,
}
)
self.set_flow_plan(plan)
response = self.client.get(url)
challenge = self.assertStageResponse(response, self.flow)
self.assertEqual(challenge["captcha_stage"]["site_key"], site_key)
response = self.client.post(
url,
{"uid_field": self.user.email, "captcha_token": "PASSED"},
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertIn(private_key, mock.request_history[0].text)
@Mocker()
def test_invalid_with_captcha(self, mock: Mocker):
"""Test with valid email and invalid captcha token in single step"""

View File

@@ -38,7 +38,7 @@ LOGGER = get_logger()
class FieldTypes(models.TextChoices):
"""Field types an Prompt can be"""
# update website/docs/add-secure-apps/flows-stages/stages/prompt/index.md
# update website/docs/add-secure-apps/flows-stages/stages/prompt/index.mdx
# Simple text field
TEXT = "text", _("Text: Simple Text input")

44
go.mod
View File

@@ -1,6 +1,6 @@
module goauthentik.io
go 1.26.0
go 1.27.0
require (
beryju.io/ldap v0.2.2
@@ -8,7 +8,7 @@ require (
github.com/avast/retry-go/v4 v4.7.0
github.com/getsentry/sentry-go v0.48.0
github.com/go-ldap/ldap/v3 v3.4.14
github.com/go-openapi/runtime v0.33.0
github.com/go-openapi/runtime v0.33.1
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
@@ -37,42 +37,42 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.25.5 // indirect
github.com/go-openapi/analysis v0.26.0 // indirect
github.com/go-openapi/errors v0.22.8 // indirect
github.com/go-openapi/jsonpointer v1.0.0 // indirect
github.com/go-openapi/jsonreference v1.0.0 // indirect
github.com/go-openapi/loads v0.25.0 // indirect
github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect
github.com/go-openapi/loads v0.25.1 // indirect
github.com/go-openapi/runtime/server-middleware v0.33.1 // indirect
github.com/go-openapi/spec v0.22.9 // indirect
github.com/go-openapi/strfmt v0.27.0 // indirect
github.com/go-openapi/swag/conv v0.27.3 // indirect
github.com/go-openapi/swag/fileutils v0.27.3 // indirect
github.com/go-openapi/swag/jsonutils v0.27.3 // indirect
github.com/go-openapi/swag/loading v0.27.3 // indirect
github.com/go-openapi/swag/mangling v0.27.3 // indirect
github.com/go-openapi/swag/pools v0.27.3 // indirect
github.com/go-openapi/swag/stringutils v0.27.3 // indirect
github.com/go-openapi/swag/typeutils v0.27.3 // indirect
github.com/go-openapi/swag/yamlutils v0.27.3 // indirect
github.com/go-openapi/validate v0.26.1 // indirect
github.com/go-openapi/swag/conv v0.28.0 // indirect
github.com/go-openapi/swag/fileutils v0.28.0 // indirect
github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
github.com/go-openapi/swag/loading v0.28.0 // indirect
github.com/go-openapi/swag/mangling v0.28.0 // indirect
github.com/go-openapi/swag/pools v0.28.0 // indirect
github.com/go-openapi/swag/stringutils v0.28.0 // indirect
github.com/go-openapi/swag/typeutils v0.28.0 // indirect
github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
github.com/go-openapi/validate v0.26.3 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.11 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.19.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/oklog/ulid/v2 v2.1.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/spf13/pflag v1.0.9 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.opentelemetry.io/otel v1.45.0 // indirect
go.opentelemetry.io/otel/metric v1.45.0 // indirect
go.opentelemetry.io/otel/trace v1.45.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/crypto v0.55.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/text v0.41.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

100
go.sum
View File

@@ -27,50 +27,50 @@ github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.25.5 h1:xPYEvTb90o1y0epuiOPAoG4QqahjP3cdp5xNlHeKJRI=
github.com/go-openapi/analysis v0.25.5/go.mod h1:d3UGtQC5uq5Kqqqis2VH09Km/v3vwsWrYkbp4gdm+Rc=
github.com/go-openapi/analysis v0.26.0 h1:1xECln1iMMmQnTjgcknC1vi1hA4KISt6IHpSwnqcuwI=
github.com/go-openapi/analysis v0.26.0/go.mod h1:40gERFi/2dyXA1FaqRRLxkv1IlC6X+GPDNd1xrYAjZE=
github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I=
github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w=
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
github.com/go-openapi/loads v0.25.0 h1:74Bc2snfaVlsHzwdQj/3gsA9XJz3daXTJVs+4ZaK7jI=
github.com/go-openapi/loads v0.25.0/go.mod h1:JFBw4SIB9+PTIFHDfcXuSSy5h6aWzjtUCrPYyx3qWU8=
github.com/go-openapi/runtime v0.33.0 h1:Dd3Oj2ig+WH8ckK95l0Wn2V8a4bH/UqWPRZVT0vc8yU=
github.com/go-openapi/runtime v0.33.0/go.mod h1:+rsupH3+TFKqmFysqkmgBOTxpVJV8eV+j9myvvea2Xw=
github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog=
github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs=
github.com/go-openapi/loads v0.25.1 h1:toKQdIDLxlqfKLLGUUmUsiTd5/X0Chzvde9EGYQP/Ac=
github.com/go-openapi/loads v0.25.1/go.mod h1:33Hen4tsKXHL45TyYojvfD5fZUFN4O1y4r/XhsRW2zc=
github.com/go-openapi/runtime v0.33.1 h1:jCvhI+wAdsn29byy+RgcPcg+j39YT6E304QOE/WqIVk=
github.com/go-openapi/runtime v0.33.1/go.mod h1:Dl5SMVRnJz+d8bX6Y1zxy0QKpqe/ysvVeUEh1nCpEZ4=
github.com/go-openapi/runtime/server-middleware v0.33.1 h1:IAeKbwWnBnpsYTpuPVS8t73ZrPpKvRZnK2iJ2KJGUV0=
github.com/go-openapi/runtime/server-middleware v0.33.1/go.mod h1:2Gej5fDxqeJxY+w38vxXYW0BgFASfgBsJ5rXwN1Fseg=
github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w=
github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0=
github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM=
github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc=
github.com/go-openapi/swag/conv v0.27.3 h1:iqJFmGEjmX3AY0lSszABFqRVqOSt99XS0LzNIMJYuhU=
github.com/go-openapi/swag/conv v0.27.3/go.mod h1:nPRmN6jgNme99hpf+nM0auDZGALWIqlwhisKPK/bQhQ=
github.com/go-openapi/swag/fileutils v0.27.3 h1:3UVoZ2RLaIs1lt+2jcKzL8RM3Yk0rmsDE9FLA/HGxFE=
github.com/go-openapi/swag/fileutils v0.27.3/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
github.com/go-openapi/swag/jsonutils v0.27.3 h1:1DEz+O82frtSMBcos/7XIn1GnpNTbsD4Bru4Dc/uhRc=
github.com/go-openapi/swag/jsonutils v0.27.3/go.mod h1:qiDCoQvzkMxrV3G8FLEdIU5L+EFYc0zcDOHWT3Yofvo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3 h1:h/eT9kmGCDdFLJF29lOhzLtF0FmP1AX2MhLJWVebsb8=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
github.com/go-openapi/swag/loading v0.27.3 h1:L9nQkEgzU7QgFQL+pLEMfGUKxeM4pWwGwbET9Z3weW0=
github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE=
github.com/go-openapi/swag/mangling v0.27.3 h1:gRzzD1PAUoLTtGMgI3KpBmCSOlTuLTFWnviLxLcTnyg=
github.com/go-openapi/swag/mangling v0.27.3/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
github.com/go-openapi/swag/pools v0.27.3 h1:gXjImP3F6/56wRRcFgEPld084Y6u2gs21ikPBt8NKBk=
github.com/go-openapi/swag/pools v0.27.3/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
github.com/go-openapi/swag/stringutils v0.27.3 h1:Ru28hnbAvN5wycALQYy8IobHvASq+FUFMlp1QzLM0JI=
github.com/go-openapi/swag/stringutils v0.27.3/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
github.com/go-openapi/swag/typeutils v0.27.3 h1:l6SSrx5eR5/WVwrGNzN6bQ9WqL04mrxNBl9YgQ3rcJ4=
github.com/go-openapi/swag/typeutils v0.27.3/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.27.3 h1:cRFCAoYtslYn9L9T0xWryHy1t7c1MACC+DMj3CLvwvs=
github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-openapi/validate v0.26.1 h1:pZSbvtRO8G2R2FpWTYRn3w8LrsNwbtaVhP2dWiBa0Us=
github.com/go-openapi/validate v0.26.1/go.mod h1:B8UMgXiQiwwQWIbmuROlwJZDPGlikPuh7iHV1vPX9Oo=
github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=
github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k=
github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU=
github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4=
github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU=
github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k=
github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM=
github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU=
github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU=
github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=
github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.1 h1:Jm+/ze2rMtbD98yen92AhATGLGREDYXG56Xr4gMjEtE=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.1/go.mod h1:YDPnwCRDu38/oJBVMBVXOUDiJ9cIeBHWvfImHaXqnv4=
github.com/go-openapi/testify/v2 v2.6.1 h1:6CNJhTjMzgaeaH8WhshcsZNPIvRemiOcFpU7seO/y7Q=
github.com/go-openapi/testify/v2 v2.6.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-openapi/validate v0.26.3 h1:OkfZgLvLDnGP2hrRGD+42WBiPWWkoHomTJ+IVI+KaDc=
github.com/go-openapi/validate v0.26.3/go.mod h1:7DOOa4raU6NRe7A8VQSKbm3VcuUIioREYHFt+er9Sk8=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
@@ -126,8 +126,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec=
github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
@@ -167,14 +167,14 @@ github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
@@ -185,8 +185,8 @@ go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -194,8 +194,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -223,8 +223,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@@ -7,7 +7,7 @@ import (
func GetTLSConfig() *tls.Config {
// Based on
// https://ssl-config.mozilla.org/#server=go&version=1.25&config=intermediate&guideline=5.7
// https://configurator.tlsref.org/#server=go&version=1.27&config=intermediate&hsts&guideline=6.0
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{
@@ -15,8 +15,7 @@ func GetTLSConfig() *tls.Config {
tls.CurveP256,
tls.CurveP384,
},
PreferServerCipherSuites: true,
CipherSuites: []uint16{},
CipherSuites: []uint16{},
}
excludedCiphers := []uint16{

View File

@@ -60,7 +60,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage: download Rust toolchain
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:f18dbc487a2c3148b62e46c394445aa87189861bca262e095496dca5b1f0ae39 AS rust-toolchain
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:ed780d7d3d789d7056855e8c7e3c37d21368ea42f39305fe431c19ae2e08d6a3 AS rust-toolchain
ARG TARGETARCH
ARG TARGETVARIANT
@@ -82,7 +82,7 @@ RUN --mount=type=bind,target=rust-toolchain.toml,src=rust-toolchain.toml \
# Stage: Download uv
FROM ghcr.io/astral-sh/uv:0.12.5@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 AS uv
# Stage: Base python image
FROM ghcr.io/goauthentik/fips-python:3.14.7-slim-trixie-fips@sha256:6abe1b469822f5760fd9857d201ba731587ba7beede2708084cb09084f65bfd9 AS python-base
FROM ghcr.io/goauthentik/fips-python:3.14.7-slim-trixie-fips@sha256:3ebb7cd9b0ba11609bf77def90b415d81e62346343022956d052a6a2c58bac4b AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \

Binary file not shown.

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-23 00:09+0000\n"
"POT-Creation-Date: 2026-08-26 00:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -4338,6 +4338,22 @@ msgstr ""
msgid "Invalid hash"
msgstr ""
#: authentik/stages/authenticator_duo/api.py
msgid "Failed to connect to Duo: TLS certificate verification failed."
msgstr ""
#: authentik/stages/authenticator_duo/api.py
msgid "Failed to connect to Duo: TLS error."
msgstr ""
#: authentik/stages/authenticator_duo/api.py
msgid "Failed to connect to Duo."
msgstr ""
#: authentik/stages/authenticator_duo/api.py
msgid "An internal error occurred while importing devices."
msgstr ""
#: authentik/stages/authenticator_duo/models.py
msgid "Duo Authenticator Setup Stage"
msgstr ""

View File

@@ -0,0 +1,62 @@
//! Client-side TLS configuration.
use std::sync::Arc;
use rustls::{
ClientConfig, DigitallySignedStruct, Error as RustlsError, SignatureScheme,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
pki_types::{CertificateDer, ServerName, UnixTime},
};
/// Build a client config that accepts any server certificate, for `AUTHENTIK_INSECURE`.
pub fn insecure_config() -> ClientConfig {
ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoCertificateVerification))
.with_no_client_auth()
}
/// Certificate verifier that accepts any server certificate.
#[derive(Debug)]
struct NoCertificateVerification;
#[expect(
clippy::missing_trait_methods,
reason = "the trait's defaulted methods are appropriate for an accept-all verifier"
)]
impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, RustlsError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, RustlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, RustlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
rustls::crypto::aws_lc_rs::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}

View File

@@ -7,6 +7,7 @@ use tracing::trace;
use crate::config;
pub mod client;
pub mod self_signed;
pub mod store;

View File

@@ -9,7 +9,7 @@ dependencies = [
"argon2-cffi==25.1.0",
"cachetools==7.1.7",
"channels==4.3.2",
"cryptography==50.0.0",
"cryptography==50.0.1",
"dacite==1.9.2",
"deepmerge==2.1.0",
"defusedxml==0.7.1",
@@ -37,7 +37,7 @@ dependencies = [
"geopy==2.5.0",
"google-api-python-client==2.198.0",
"gssapi==1.11.1",
"gunicorn==26.0.0",
"gunicorn==26.1.0",
"jsonpatch==1.33",
"jwcrypto==1.5.8",
"kubernetes==36.0.3",
@@ -105,7 +105,7 @@ dev = [
"ruff==0.16.4",
"selenium==4.47.0",
"types-channels==4.3.0.20260518",
"types-docker==7.2.0.20260813",
"types-docker==7.2.0.20260819",
"types-jwcrypto==1.5.8.20260720",
"types-ldap3==2.9.13.20260518",
"types-requests==2.33.0.20260712",

View File

@@ -1,6 +1,6 @@
use std::{fmt::Display, sync::Arc};
use ak_common::{Arbiter, Tasks, VERSION, api, arbiter, authentik_build_hash, config};
use ak_common::{Arbiter, Tasks, VERSION, api, arbiter, authentik_build_hash, config, tls};
use axum::http::{HeaderValue, header::AUTHORIZATION};
use eyre::{Result, eyre};
use futures::{Sink, SinkExt as _, Stream, StreamExt as _};
@@ -13,7 +13,10 @@ use tokio::{
signal::unix::SignalKind,
time::{Duration, interval, sleep},
};
use tokio_tungstenite::tungstenite::{Error as WsError, Message, client::IntoClientRequest as _};
use tokio_tungstenite::{
Connector,
tungstenite::{Error as WsError, Message, client::IntoClientRequest as _},
};
use tracing::{debug, info, instrument, trace, warn};
use url::Url;
@@ -149,11 +152,14 @@ async fn watch_events_inner<O: Outpost>(
warn!(?err, "failed to refresh");
}
let host = if controller.is_embedded() {
Url::parse(&format!("http://localhost{}", config::get().web.path))?
let (host, insecure) = if controller.is_embedded() {
(
Url::parse(&format!("http://localhost{}", config::get().web.path))?,
false,
)
} else {
let server_config = api::ServerConfig::new()?;
server_config.host
(server_config.host, server_config.insecure)
};
let ws_url = build_ws_url(
@@ -163,7 +169,7 @@ async fn watch_events_inner<O: Outpost>(
attempt,
)?;
debug!(url = %ws_url, "connecting to websocket");
debug!(url = %ws_url, insecure, "connecting to websocket");
let mut request = ws_url.into_client_request()?;
let token = controller
.api_config
@@ -195,7 +201,11 @@ async fn watch_events_inner<O: Outpost>(
let (write, read) = ws_stream.split();
(Box::new(write), Box::new(read))
} else {
let (ws_stream, _response) = tokio_tungstenite::connect_async(request).await?;
let connector =
insecure.then(|| Connector::Rustls(Arc::new(tls::client::insecure_config())));
let (ws_stream, _response) =
tokio_tungstenite::connect_async_tls_with_config(request, None, false, connector)
.await?;
let (write, read) = ws_stream.split();
(Box::new(write), Box::new(read))
};

View File

@@ -1,7 +1,6 @@
//! HTTP client used to forward requests to upstream application servers.
use std::sync::Arc;
use ak_common::tls;
use axum::body::Body;
use eyre::Result;
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
@@ -9,11 +8,6 @@ use hyper_util::{
client::legacy::{Client, connect::HttpConnector},
rt::TokioExecutor,
};
use rustls::{
ClientConfig, DigitallySignedStruct, Error as RustlsError, SignatureScheme,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
pki_types::{CertificateDer, ServerName, UnixTime},
};
/// Client forwarding to upstream servers (HTTP or HTTPS, with optional upgrades).
pub(super) type UpstreamClient = Client<HttpsConnector<HttpConnector>, Body>;
@@ -23,7 +17,7 @@ pub(super) type UpstreamClient = Client<HttpsConnector<HttpConnector>, Body>;
pub(super) fn build_client(insecure: bool) -> Result<UpstreamClient> {
let builder = HttpsConnectorBuilder::new();
let connector = if insecure {
builder.with_tls_config(insecure_tls_config())
builder.with_tls_config(tls::client::insecure_config())
} else {
builder.with_native_roots()?
}
@@ -37,55 +31,3 @@ pub(super) fn build_client(insecure: bool) -> Result<UpstreamClient> {
.http1_title_case_headers(true)
.build(connector))
}
fn insecure_tls_config() -> ClientConfig {
ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoCertificateVerification))
.with_no_client_auth()
}
/// Certificate verifier that accepts any upstream certificate.
#[derive(Debug)]
struct NoCertificateVerification;
#[expect(
clippy::missing_trait_methods,
reason = "the trait's defaulted methods are appropriate for an accept-all verifier"
)]
impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, RustlsError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, RustlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, RustlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
rustls::crypto::aws_lc_rs::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}

View File

@@ -151,7 +151,7 @@ async fn forward_request(
.insert(X_FORWARDED_PROTO, HeaderValue::from_str(scheme.as_ref())?);
if is_websocket_upgrade(request.headers()) {
return handle_websocket_upgrade(request, server).await;
return handle_websocket_upgrade(request, &server.socket_path).await;
}
if let Some(tls_state) = tls_state
@@ -351,7 +351,7 @@ mod tests {
}
mod websockets {
use std::sync::Arc;
use std::path::Path;
use ak_axum::error::{AppError, Result};
use axum::{
@@ -360,22 +360,24 @@ mod websockets {
http::{
HeaderMap, HeaderValue, StatusCode,
header::{
CONNECTION, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_VERSION, UPGRADE,
CONNECTION, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY,
SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, UPGRADE,
},
},
response::{IntoResponse as _, Response},
};
use futures::{SinkExt as _, StreamExt as _};
use hyper::upgrade::OnUpgrade;
use hyper_util::rt::TokioIo;
use tokio::{net::UnixStream, sync::mpsc};
use tokio_tungstenite::{
WebSocketStream, client_async,
tungstenite::{Message, handshake::derive_accept_key, protocol::Role},
tungstenite::{
Error as TungsteniteError, Message, handshake::derive_accept_key, protocol::Role,
},
};
use tracing::{debug, trace, warn};
use crate::server::Server;
pub(super) fn is_websocket_upgrade(headers: &HeaderMap<HeaderValue>) -> bool {
let has_upgrade = headers
.get(UPGRADE)
@@ -397,8 +399,8 @@ mod websockets {
}
pub(super) async fn handle_websocket_upgrade(
request: Request,
server: Arc<Server>,
mut request: Request,
socket_path: &Path,
) -> Result<Response> {
let Some(ws_key) = request
.headers()
@@ -410,6 +412,8 @@ mod websockets {
let ws_accept = derive_accept_key(ws_key.as_bytes());
request.headers_mut().remove(SEC_WEBSOCKET_EXTENSIONS);
let path_q = request
.uri()
.path_and_query()
@@ -424,15 +428,28 @@ mod websockets {
}
let ws_request = ws_request.body(())?;
let response = Response::builder()
let stream = UnixStream::connect(socket_path).await?;
let (upstream_ws, upstream_response) = match client_async(ws_request, stream).await {
Ok(upstream) => upstream,
Err(TungsteniteError::Http(upstream_response)) => {
return Ok((upstream_response.status(), "").into_response());
}
Err(err) => return Err(err.into()),
};
let mut response = Response::builder()
.status(StatusCode::SWITCHING_PROTOCOLS)
.header(UPGRADE, "websocket")
.header(CONNECTION, "upgrade")
.header(SEC_WEBSOCKET_ACCEPT, ws_accept)
.body(Body::empty())?;
.header(SEC_WEBSOCKET_ACCEPT, ws_accept);
if let Some(selected) = upstream_response.headers().get(SEC_WEBSOCKET_PROTOCOL) {
response = response.header(SEC_WEBSOCKET_PROTOCOL, selected);
}
let response = response.body(Body::empty())?;
let client_upgrade = hyper::upgrade::on(&mut request);
tokio::spawn(async move {
if let Err(err) = handle_websocket_connection(request, server, ws_request).await {
if let Err(err) = handle_websocket_connection(client_upgrade, upstream_ws).await {
warn!("WebSocket connection error: {}", err.0);
}
});
@@ -441,19 +458,15 @@ mod websockets {
}
async fn handle_websocket_connection(
request: Request,
server: Arc<Server>,
ws_request: tokio_tungstenite::tungstenite::handshake::client::Request,
client_upgrade: OnUpgrade,
upstream_ws: WebSocketStream<UnixStream>,
) -> Result<()> {
let upgraded = hyper::upgrade::on(request).await?;
let io = TokioIo::new(upgraded);
let client_ws = WebSocketStream::from_raw_socket(io, Role::Server, None).await;
let upstream_ws = {
let stream = UnixStream::connect(&server.socket_path).await?;
let (ws_stream, _) = client_async(ws_request, stream).await?;
ws_stream
};
let client_ws = WebSocketStream::from_raw_socket(
TokioIo::new(client_upgrade.await?),
Role::Server,
None,
)
.await;
let (mut client_sender, mut client_receiver) = client_ws.split();
let (mut upstream_sender, mut upstream_receiver) = upstream_ws.split();
@@ -541,4 +554,205 @@ mod websockets {
Ok(())
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use axum::http::header::HOST;
use tempfile::TempDir;
use tokio::{
io::{AsyncReadExt as _, AsyncWriteExt as _},
net::UnixListener,
sync::oneshot,
time::timeout,
};
use tokio_tungstenite::{
accept_hdr_async,
tungstenite::handshake::server::{
ErrorResponse, Request as UpstreamRequest, Response as UpstreamResponse,
},
};
use super::*;
const WS_KEY: &str = "dGhlIHNhbXBsZSBub25jZQ==";
/// Stands in for the ASGI application on `socket_path`, answering the
/// handshake the way uvicorn's wsproto implementation does: it names the
/// subprotocol the application selected, and accepts `permessage-deflate`
/// whenever the handshake offers it.
///
/// Resolves to the headers the upstream handshake was made with.
fn spawn_upstream(
socket_path: &Path,
subprotocol: Option<&'static str>,
) -> oneshot::Receiver<HeaderMap> {
let listener = UnixListener::bind(socket_path).expect("failed to bind upstream socket");
let (headers_tx, headers_rx) = oneshot::channel();
tokio::spawn(async move {
let (stream, _) = listener.accept().await.expect("failed to accept");
#[expect(
clippy::result_large_err,
reason = "the callback signature is dictated by tungstenite"
)]
let callback = |request: &UpstreamRequest, mut response: UpstreamResponse| {
let headers = request.headers().clone();
if let Some(subprotocol) = subprotocol {
response.headers_mut().insert(
SEC_WEBSOCKET_PROTOCOL,
HeaderValue::from_static(subprotocol),
);
}
if headers.contains_key(SEC_WEBSOCKET_EXTENSIONS) {
response.headers_mut().insert(
SEC_WEBSOCKET_EXTENSIONS,
HeaderValue::from_static("permessage-deflate"),
);
}
drop(headers_tx.send(headers));
Ok::<_, ErrorResponse>(response)
};
let _upstream = accept_hdr_async(stream, callback)
.await
.expect("upstream handshake failed");
// Hold the connection open for the rest of the test.
std::future::pending::<()>().await;
});
headers_rx
}
/// Rejects the handshake instead of upgrading, the way the application
/// answers when a consumer denies the connection.
fn spawn_rejecting_upstream(socket_path: &Path) {
let listener = UnixListener::bind(socket_path).expect("failed to bind upstream socket");
tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.expect("failed to accept");
let mut request = Vec::new();
let mut byte = [0_u8; 1];
while !request.ends_with(b"\r\n\r\n") {
if stream.read(&mut byte).await.expect("failed to read") == 0 {
break;
}
request.extend_from_slice(&byte);
}
stream
.write_all(b"HTTP/1.1 403 Forbidden\r\ncontent-length: 0\r\n\r\n")
.await
.expect("failed to write rejection");
});
}
fn client_request(subprotocol: Option<&str>, extensions: Option<&str>) -> Request {
let mut builder = Request::builder()
// `forward_request` rewrites the URI before handing the request over.
.uri("http://localhost:8000/ws/rac/connection-token/")
.header(HOST, "authentik.company")
.header(UPGRADE, "websocket")
.header(CONNECTION, "upgrade")
.header(SEC_WEBSOCKET_KEY, WS_KEY)
.header(SEC_WEBSOCKET_VERSION, "13");
if let Some(subprotocol) = subprotocol {
builder = builder.header(SEC_WEBSOCKET_PROTOCOL, subprotocol);
}
if let Some(extensions) = extensions {
builder = builder.header(SEC_WEBSOCKET_EXTENSIONS, extensions);
}
builder
.body(Body::empty())
.expect("failed to build client request")
}
/// RAC is the only consumer that negotiates a subprotocol, and browsers
/// fail a connection when they offered one and the 101 names none.
#[tokio::test]
async fn upgrade_echoes_upstream_subprotocol() {
let dir = TempDir::new().expect("failed to create temp dir");
let socket_path = dir.path().join("authentik.sock");
let _upstream = spawn_upstream(&socket_path, Some("guacamole"));
let response =
handle_websocket_upgrade(client_request(Some("guacamole"), None), &socket_path)
.await
.expect("upgrade should succeed");
assert_eq!(response.status(), StatusCode::SWITCHING_PROTOCOLS);
assert_eq!(
response.headers().get(SEC_WEBSOCKET_PROTOCOL),
Some(&HeaderValue::from_static("guacamole")),
"the 101 must name the subprotocol the application selected"
);
}
/// The bridge relays frames unchanged and cannot decode a compressed one,
/// so the client's extension offer must not reach the application.
#[tokio::test]
async fn upgrade_does_not_offer_extensions_upstream() {
let dir = TempDir::new().expect("failed to create temp dir");
let socket_path = dir.path().join("authentik.sock");
let upstream = spawn_upstream(&socket_path, Some("guacamole"));
let response = handle_websocket_upgrade(
client_request(
Some("guacamole"),
Some("permessage-deflate; client_max_window_bits"),
),
&socket_path,
)
.await
.expect("upgrade should succeed");
assert!(
!response.headers().contains_key(SEC_WEBSOCKET_EXTENSIONS),
"the 101 must not name an extension"
);
let upstream_headers = timeout(Duration::from_secs(5), upstream)
.await
.expect("upstream did not receive a handshake")
.expect("upstream dropped the handshake");
assert!(
!upstream_headers.contains_key(SEC_WEBSOCKET_EXTENSIONS),
"the upstream handshake must not offer extensions"
);
}
/// Every other consumer accepts without a subprotocol; naming one the
/// client did not ask for makes browsers drop the connection.
#[tokio::test]
async fn upgrade_without_subprotocol_names_none() {
let dir = TempDir::new().expect("failed to create temp dir");
let socket_path = dir.path().join("authentik.sock");
let _upstream = spawn_upstream(&socket_path, None);
let response = handle_websocket_upgrade(client_request(None, None), &socket_path)
.await
.expect("upgrade should succeed");
assert_eq!(response.status(), StatusCode::SWITCHING_PROTOCOLS);
assert!(
!response.headers().contains_key(SEC_WEBSOCKET_PROTOCOL),
"the 101 must not name a subprotocol that was not requested"
);
}
/// An application that denies the connection must not be preceded by a
/// 101 the client would have to tear down immediately.
#[tokio::test]
async fn upgrade_propagates_upstream_rejection() {
let dir = TempDir::new().expect("failed to create temp dir");
let socket_path = dir.path().join("authentik.sock");
spawn_rejecting_upstream(&socket_path);
let response =
handle_websocket_upgrade(client_request(Some("guacamole"), None), &socket_path)
.await
.expect("upgrade should return a response");
assert_eq!(response.status(), StatusCode::FORBIDDEN);
}
}
}

View File

@@ -14,7 +14,7 @@ services:
environment:
VIDEO_READY_PORT: 9912
mailpit:
image: docker.io/axllent/mailpit:v1.30.7
image: docker.io/axllent/mailpit:v1.31.0
ports:
- 1025:1025
- 8025:8025

103
uv.lock generated
View File

@@ -371,7 +371,7 @@ requires-dist = [
{ name = "argon2-cffi", specifier = "==25.1.0" },
{ name = "cachetools", specifier = "==7.1.7" },
{ name = "channels", specifier = "==4.3.2" },
{ name = "cryptography", specifier = "==50.0.0" },
{ name = "cryptography", specifier = "==50.0.1" },
{ name = "dacite", specifier = "==1.9.2" },
{ name = "deepmerge", specifier = "==2.1.0" },
{ name = "defusedxml", specifier = "==0.7.1" },
@@ -399,7 +399,7 @@ requires-dist = [
{ name = "geopy", specifier = "==2.5.0" },
{ name = "google-api-python-client", specifier = "==2.198.0" },
{ name = "gssapi", specifier = "==1.11.1" },
{ name = "gunicorn", specifier = "==26.0.0" },
{ name = "gunicorn", specifier = "==26.1.0" },
{ name = "jsonpatch", specifier = "==1.33" },
{ name = "jwcrypto", specifier = "==1.5.8" },
{ name = "kubernetes", specifier = "==36.0.3" },
@@ -467,7 +467,7 @@ dev = [
{ name = "ruff", specifier = "==0.16.4" },
{ name = "selenium", specifier = "==4.47.0" },
{ name = "types-channels", specifier = "==4.3.0.20260518" },
{ name = "types-docker", specifier = "==7.2.0.20260813" },
{ name = "types-docker", specifier = "==7.2.0.20260819" },
{ name = "types-jwcrypto", specifier = "==1.5.8.20260720" },
{ name = "types-ldap3", specifier = "==2.9.13.20260518" },
{ name = "types-requests", specifier = "==2.33.0.20260712" },
@@ -1000,52 +1000,52 @@ wheels = [
[[package]]
name = "cryptography"
version = "50.0.0"
version = "50.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" }
sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" },
{ url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" },
{ url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" },
{ url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" },
{ url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" },
{ url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" },
{ url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" },
{ url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" },
{ url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" },
{ url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" },
{ url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" },
{ url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" },
{ url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" },
{ url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" },
{ url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" },
{ url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" },
{ url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" },
{ url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" },
{ url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" },
{ url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" },
{ url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" },
{ url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" },
{ url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" },
{ url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" },
{ url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" },
{ url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" },
{ url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" },
{ url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" },
{ url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" },
{ url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" },
{ url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" },
{ url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" },
{ url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" },
{ url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" },
{ url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" },
{ url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" },
{ url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" },
{ url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" },
{ url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" },
{ url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" },
{ url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" },
{ url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" },
{ url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" },
{ url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" },
{ url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" },
{ url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" },
{ url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" },
{ url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" },
{ url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" },
{ url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" },
{ url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" },
{ url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" },
{ url = "https://files.pythonhosted.org/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" },
{ url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" },
{ url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" },
{ url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" },
{ url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" },
{ url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" },
{ url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" },
{ url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" },
{ url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" },
{ url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" },
{ url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" },
{ url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" },
{ url = "https://files.pythonhosted.org/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" },
{ url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" },
{ url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" },
{ url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" },
{ url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" },
{ url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" },
{ url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" },
{ url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" },
{ url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" },
{ url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" },
{ url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" },
{ url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" },
{ url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" },
{ url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" },
]
[[package]]
@@ -1764,14 +1764,11 @@ wheels = [
[[package]]
name = "gunicorn"
version = "26.0.0"
version = "26.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" }
sdist = { url = "https://files.pythonhosted.org/packages/38/b8/ec4ba3f6cace4091c34e27478b576bb80f2f06fab80fd42c0ecc785b308f/gunicorn-26.1.0.tar.gz", hash = "sha256:1413d777bf99d31ebeb08acd354b01f1ecc44db0aa7b811ae7b86c669232e4f7", size = 755923, upload-time = "2026-08-18T11:49:39.438Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" },
{ url = "https://files.pythonhosted.org/packages/19/dc/7a55fc605543fd5cb11c003fbbb21a1911d5e88a582cce6c5e063bf5c176/gunicorn-26.1.0-py3-none-any.whl", hash = "sha256:9f45bcddec5e9dc7a25a3bdccb0c6832f11fd5d4739b1ee36c8d2fec25f1dc86", size = 216237, upload-time = "2026-08-18T11:49:38.001Z" },
]
[[package]]
@@ -3690,15 +3687,15 @@ wheels = [
[[package]]
name = "types-docker"
version = "7.2.0.20260813"
version = "7.2.0.20260819"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "types-requests" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/48/a1eeaffe58bfc0d1fd61fc8e1c6a450e73e4ec7181a3720b2a28bce6fabc/types_docker-7.2.0.20260813.tar.gz", hash = "sha256:c42eae5a10afad99864aec144daf62f33422baf265f58be9112ffc037f0e9783", size = 36836, upload-time = "2026-08-13T03:56:42.622Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/b6/4ccc887c1ef5974699d4e0c1904d72534f99c7bb6c88bc24b8000a4c81bd/types_docker-7.2.0.20260819.tar.gz", hash = "sha256:3fe9a80f1f1ba271d9188b58f46c743eede65e1c46bb82ca87101725a2dc7a22", size = 36846, upload-time = "2026-08-19T02:47:25.481Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/10/878e713aa6176480a70d759e518bc3382f5e31ffc0f8913834c5dd839aaa/types_docker-7.2.0.20260813-py3-none-any.whl", hash = "sha256:8d25c84424ec911cf71eb023ab271f9f6f214e7d2515ed9ce61d6be13fa6d507", size = 51231, upload-time = "2026-08-13T03:56:41.636Z" },
{ url = "https://files.pythonhosted.org/packages/a4/ea/4c1dd437b82b55c6333e31617cc9befd07d19392404d861e290cc8d6b8c3/types_docker-7.2.0.20260819-py3-none-any.whl", hash = "sha256:b25951651f2bdad88e194067bb8a523d2963a316caef699a2c2a4e30c3394ae2", size = 51258, upload-time = "2026-08-19T02:47:24.4Z" },
]
[[package]]

View File

@@ -113,6 +113,31 @@ export function styleLoaderPlugin({
};
});
/**
* Handle plain `.css` text imports, i.e. a component's `static styles`.
*
* These bypass ESBuild's CSS pipeline entirely, so their authored source —
* native nesting included — reaches ShadyCSS verbatim. Lower the nesting
* here for the same reason the bundled path does.
*
* @see {@linkcode CSSNamespace.Bundled} for the rationale.
*/
build.onLoad({ filter: /\.css$/, namespace: "file" }, async (args) => {
const cssContent = await readFile(args.path, "utf8");
const { code } = await build.esbuild.transform(cssContent, {
loader: "css",
minify: build.initialOptions.minify || false,
supported: { nesting: false },
logLevel: "silent",
});
return {
contents: code,
loader: "text",
};
});
/**
* Handle `with { type: "bundled-text" }` imports.
*

View File

@@ -20,7 +20,7 @@
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-swc": "^0.4.1",
"@swc/cli": "^0.8.1",
"@swc/core": "^1.16.0",
"@swc/core": "^1.16.1",
"@webcomponents/template": "^1.5.1",
"base64-js": "^1.5.1",
"core-js": "^3.49.0",
@@ -32,15 +32,15 @@
"weakmap-polyfill": "^2.0.4"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "^1.16.0",
"@swc/core-darwin-x64": "^1.16.0",
"@swc/core-linux-arm-gnueabihf": "^1.16.0",
"@swc/core-linux-arm64-gnu": "^1.16.0",
"@swc/core-linux-arm64-musl": "^1.16.0",
"@swc/core-linux-x64-gnu": "^1.16.0",
"@swc/core-linux-x64-musl": "^1.16.0",
"@swc/core-win32-arm64-msvc": "^1.16.0",
"@swc/core-win32-ia32-msvc": "^1.16.0",
"@swc/core-win32-x64-msvc": "^1.16.0"
"@swc/core-darwin-arm64": "^1.16.1",
"@swc/core-darwin-x64": "^1.16.1",
"@swc/core-linux-arm-gnueabihf": "^1.16.1",
"@swc/core-linux-arm64-gnu": "^1.16.1",
"@swc/core-linux-arm64-musl": "^1.16.1",
"@swc/core-linux-x64-gnu": "^1.16.1",
"@swc/core-linux-x64-musl": "^1.16.1",
"@swc/core-win32-arm64-msvc": "^1.16.1",
"@swc/core-win32-ia32-msvc": "^1.16.1",
"@swc/core-win32-x64-msvc": "^1.16.1"
}
}

158
web/pnpm-lock.yaml generated
View File

@@ -481,13 +481,13 @@ importers:
version: 16.0.3(rollup@4.62.4)
'@rollup/plugin-swc':
specifier: ^0.4.1
version: 0.4.1(@swc/core@1.16.0)(rollup@4.62.4)
version: 0.4.1(@swc/core@1.16.1)(rollup@4.62.4)
'@swc/cli':
specifier: ^0.8.1
version: 0.8.1(@swc/core@1.16.0)(supports-color@10.2.2)
version: 0.8.1(@swc/core@1.16.1)(supports-color@10.2.2)
'@swc/core':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@webcomponents/template':
specifier: ^1.5.1
version: 1.5.1
@@ -517,35 +517,35 @@ importers:
version: 2.0.4
optionalDependencies:
'@swc/core-darwin-arm64':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-darwin-x64':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-linux-arm-gnueabihf':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-linux-arm64-gnu':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-linux-arm64-musl':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-linux-x64-gnu':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-linux-x64-musl':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-win32-arm64-msvc':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-win32-ia32-msvc':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
'@swc/core-win32-x64-msvc':
specifier: ^1.16.0
version: 1.16.0
specifier: ^1.16.1
version: 1.16.1
packages:
@@ -2394,86 +2394,86 @@ packages:
chokidar:
optional: true
'@swc/core-darwin-arm64@1.16.0':
resolution: {integrity: sha512-SJQPl+xG/zB8bNjC/gTg3WOmOvz7EzlQD+VShfCKFYPNr2qvb+vATUY11vYEjnMWCn6wV8H8eAtjQrVflYyX5A==}
'@swc/core-darwin-arm64@1.16.1':
resolution: {integrity: sha512-zlJblJ8ncErD43lKdxjbUaUskJQf+LxiPXYcWXD8/8ZMV+7uuAT+CwjciLXpyZBd5Pq/S726bMpeeAwSeL1hhg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.16.0':
resolution: {integrity: sha512-ql2JVch8V5t1i+HxiiuD4oVDI1dOku4/e3QiCkplONrm3SLitqNAP+nztHN51fSG2IgGuOwpAi3hgA+ukT5yQg==}
'@swc/core-darwin-x64@1.16.1':
resolution: {integrity: sha512-IN0BmPWb0YAh/17mmlWB/HDBtTw2MfuW4hulf/tQAgTQBRH17l+z499bNJLK6LizSjqs0P7V+jU38Zj+vJC1DA==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.16.0':
resolution: {integrity: sha512-PcdDBaRbe39y37h1rXVkhNy7mEU7f8b34KD761C68R23EsfMsj5oDPVddRzGdSRAvwwSfH0WSNEHgYmc/AJipg==}
'@swc/core-linux-arm-gnueabihf@1.16.1':
resolution: {integrity: sha512-EYgrx2YOCQ2Twz2S793kqNjPkpvYVUPzzR95bIb7by+VQcyaai4lZZ2iz/tZvcFVKSNcN3/JTKwx+aBn2ZL52A==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.16.0':
resolution: {integrity: sha512-t21IUztHQ/COucy7Kk9eIlehmq08H/hYq7aRA6fZox3S5ddi6TxWPK6e5S/+aTCf6+Od9qQ+LIpjHMiTy737vA==}
'@swc/core-linux-arm64-gnu@1.16.1':
resolution: {integrity: sha512-moyKm0YZlHdHohzm1YwgAyesqnE853rO0REMfJLFAova51wF9BNi+3ZW2PeS7Vqvn6HeJuepLpAHbBdZctxpHA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.16.0':
resolution: {integrity: sha512-d9+iajbMB87b0umgbP+Gy3yBDSDgty4Q6H5pZ8fgTb/dOoKIwwynP4L4kvWCOFg2i49kxmAAUs1uJZh9s0E+RQ==}
'@swc/core-linux-arm64-musl@1.16.1':
resolution: {integrity: sha512-kKGBO9wdapiSzuf5ZzZ2fYtlu1BNSYtIIUxvH1ir/gcelTOREEHGDCLTDFx/2Knf878nU11A40z7LxwasEFxqA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-ppc64-gnu@1.16.0':
resolution: {integrity: sha512-QRpeKGOg+B0qmo3BFU+6rL/gpoKYYJ7OFSMf5DNMafohYZ/iq2qvAH9Gcrf8NxROj3iooKOVewJ+YgahH1nSLw==}
'@swc/core-linux-ppc64-gnu@1.16.1':
resolution: {integrity: sha512-nZ6qahtLxC3PM54cWOQZHxt4lTCF/3J4LIoWWzz6v7A+rLs8Dx54anYQf7mH3eIi8KlNpgKci/ie8ZSqFN8O7A==}
engines: {node: '>=10'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@swc/core-linux-s390x-gnu@1.16.0':
resolution: {integrity: sha512-q+Vr/hmHCcRXT/WFzOJC+T6GGEEtq2iaTtmyLfxO7yzu4ckgcqSNkg9m181wfNhuMwfNBoBhOfwQCnLsGZ5F4g==}
'@swc/core-linux-s390x-gnu@1.16.1':
resolution: {integrity: sha512-4ji5PNzhYq193Z4/4xUaSoNJza6iCkDJSzhetrbB6KOYxsr+kxtQr8ePWhMJUiMt6JUWtXaZ1PYT8FhtED+nGA==}
engines: {node: '>=10'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-gnu@1.16.0':
resolution: {integrity: sha512-DWVBc3QnpsSgKoq8N4rmZeZa5r/XrHdLkITsExN/tvTdqPtAPDPt+Ysy33OfgBlyN8lNe4xwsXWe6DXlRkJeRQ==}
'@swc/core-linux-x64-gnu@1.16.1':
resolution: {integrity: sha512-VJQxqrisHV+B394IgrOu8YsIIXZgffnf5tO+yc9Z/hoUpuZEvuQTjWwlnpZdpyD+0nx6LTD1/3k646JYm43yJA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.16.0':
resolution: {integrity: sha512-6XCgDSc1HPf/5dpjvABhKHICiBcsuZyW3hQMkn8sxel0TqprkJGp+H4iaBYIUTPixhrBub2hBPtfjcZLE6yL3w==}
'@swc/core-linux-x64-musl@1.16.1':
resolution: {integrity: sha512-r9oV1mwxxsIGcLV1IQ/tw76MW3doatKze1QFWuC+a7QqJUkhY/bKTSVk6NpKKUGm2LDsE33Va8VqSClfA7vSiQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.16.0':
resolution: {integrity: sha512-T/+9VVCZJ3AKEth9IP3U9AJ2YscQq+7LUqRTvfR4a2q36+Ri22oOwUizpAKOqQ42vb2Y/kOa4TOcJOfHoDIT/w==}
'@swc/core-win32-arm64-msvc@1.16.1':
resolution: {integrity: sha512-6huNRessoBLxWEqBm5zJXyCQ27TO7anvkdiuQ5MDO4CJni0nOXEqKtV9RllQ2TdyENKKsUMXVnIfW2hIXx/R5Q==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.16.0':
resolution: {integrity: sha512-Pr1lsR/PMs8ndL0UWMrW8nLZ7H7sspIxBRDdjL8f+YJ/FJNASgzfunbVVXAqj0csgIJYHPZy+OW9smjFmk1Rcg==}
'@swc/core-win32-ia32-msvc@1.16.1':
resolution: {integrity: sha512-OVKJFUzphrGmsh+BGtcZDesx0YryV7/Yvy5XGgTqnrZfjnyfcr5uaqYQugCckdIlupc5Vs3XtDjRAj12z4ZPlw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.16.0':
resolution: {integrity: sha512-ktdeYLgOQdaonvsj5tJijqgpb0wk7gfF80wCFVA0kucI1hhSUIyfcGbjo5+9sdqv38OhMnTdLoA6xbqgOgPQjw==}
'@swc/core-win32-x64-msvc@1.16.1':
resolution: {integrity: sha512-Bt+VIhWYCGk4urklnkkteLUOeLv1VxigwTCeB/xC6rBZxY6IIKdDwCJf6on3E3SUGsIqmQS6QqtuJQc1VxF4Aw==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.16.0':
resolution: {integrity: sha512-zSdvEHxBg00WhUNtW/u58hhcdR33gjtMQvOBo8F7POWJDyjRCt/miKfhidT3hCc/118RUwNnlEAmxiihFMbK4Q==}
'@swc/core@1.16.1':
resolution: {integrity: sha512-nUaeu91O5QZKrQdaDCHd402ogUIoNOOjpkZNq0UomWK0G6gDaGmLhvddF1/3BXf5O8aLyo6ZPY/aMDWvaJQ/hg==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
@@ -7696,10 +7696,10 @@ snapshots:
optionalDependencies:
rollup: 4.62.4
'@rollup/plugin-swc@0.4.1(@swc/core@1.16.0)(rollup@4.62.4)':
'@rollup/plugin-swc@0.4.1(@swc/core@1.16.1)(rollup@4.62.4)':
dependencies:
'@rollup/pluginutils': 5.4.0(rollup@4.62.4)
'@swc/core': 1.16.0
'@swc/core': 1.16.1
smob: 1.6.2
optionalDependencies:
rollup: 4.62.4
@@ -8348,9 +8348,9 @@ snapshots:
dependencies:
apg-lite: 1.0.5
'@swc/cli@0.8.1(@swc/core@1.16.0)(supports-color@10.2.2)':
'@swc/cli@0.8.1(@swc/core@1.16.1)(supports-color@10.2.2)':
dependencies:
'@swc/core': 1.16.0
'@swc/core': 1.16.1
'@swc/counter': 0.1.3
'@xhmikosr/bin-wrapper': 14.4.0(supports-color@10.2.2)
commander: 8.3.0
@@ -8365,59 +8365,59 @@ snapshots:
- react-native-b4a
- supports-color
'@swc/core-darwin-arm64@1.16.0':
'@swc/core-darwin-arm64@1.16.1':
optional: true
'@swc/core-darwin-x64@1.16.0':
'@swc/core-darwin-x64@1.16.1':
optional: true
'@swc/core-linux-arm-gnueabihf@1.16.0':
'@swc/core-linux-arm-gnueabihf@1.16.1':
optional: true
'@swc/core-linux-arm64-gnu@1.16.0':
'@swc/core-linux-arm64-gnu@1.16.1':
optional: true
'@swc/core-linux-arm64-musl@1.16.0':
'@swc/core-linux-arm64-musl@1.16.1':
optional: true
'@swc/core-linux-ppc64-gnu@1.16.0':
'@swc/core-linux-ppc64-gnu@1.16.1':
optional: true
'@swc/core-linux-s390x-gnu@1.16.0':
'@swc/core-linux-s390x-gnu@1.16.1':
optional: true
'@swc/core-linux-x64-gnu@1.16.0':
'@swc/core-linux-x64-gnu@1.16.1':
optional: true
'@swc/core-linux-x64-musl@1.16.0':
'@swc/core-linux-x64-musl@1.16.1':
optional: true
'@swc/core-win32-arm64-msvc@1.16.0':
'@swc/core-win32-arm64-msvc@1.16.1':
optional: true
'@swc/core-win32-ia32-msvc@1.16.0':
'@swc/core-win32-ia32-msvc@1.16.1':
optional: true
'@swc/core-win32-x64-msvc@1.16.0':
'@swc/core-win32-x64-msvc@1.16.1':
optional: true
'@swc/core@1.16.0':
'@swc/core@1.16.1':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.28
optionalDependencies:
'@swc/core-darwin-arm64': 1.16.0
'@swc/core-darwin-x64': 1.16.0
'@swc/core-linux-arm-gnueabihf': 1.16.0
'@swc/core-linux-arm64-gnu': 1.16.0
'@swc/core-linux-arm64-musl': 1.16.0
'@swc/core-linux-ppc64-gnu': 1.16.0
'@swc/core-linux-s390x-gnu': 1.16.0
'@swc/core-linux-x64-gnu': 1.16.0
'@swc/core-linux-x64-musl': 1.16.0
'@swc/core-win32-arm64-msvc': 1.16.0
'@swc/core-win32-ia32-msvc': 1.16.0
'@swc/core-win32-x64-msvc': 1.16.0
'@swc/core-darwin-arm64': 1.16.1
'@swc/core-darwin-x64': 1.16.1
'@swc/core-linux-arm-gnueabihf': 1.16.1
'@swc/core-linux-arm64-gnu': 1.16.1
'@swc/core-linux-arm64-musl': 1.16.1
'@swc/core-linux-ppc64-gnu': 1.16.1
'@swc/core-linux-s390x-gnu': 1.16.1
'@swc/core-linux-x64-gnu': 1.16.1
'@swc/core-linux-x64-musl': 1.16.1
'@swc/core-win32-arm64-msvc': 1.16.1
'@swc/core-win32-ia32-msvc': 1.16.1
'@swc/core-win32-x64-msvc': 1.16.1
'@swc/counter@0.1.3': {}

View File

@@ -15,7 +15,6 @@ import {
} from "./navigation/sidebar.js";
import { isAPIResultReady } from "#common/api/responses";
import { configureSentry } from "#common/sentry/index";
import { isGuest } from "#common/users";
import { WebsocketClient } from "#common/ws/WebSocketClient";
@@ -131,8 +130,6 @@ export class AdminInterface extends WithCapabilitiesConfig(
//#region Lifecycle
constructor() {
configureSentry();
super();
WebsocketClient.connect();

View File

@@ -24,7 +24,7 @@ import { AKApplicationWizard } from "#admin/applications/wizard/ak-application-w
import { Application, CoreApi, PoliciesApi } from "@goauthentik/api";
import MDApplication from "~docs/add-secure-apps/applications/index.md";
import MDApplication from "~docs/add-secure-apps/applications/index.mdx";
import { msg, str } from "@lit/localize";
import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";

View File

@@ -1,3 +1,4 @@
import "#common/sentry/apply";
import "#elements/messages/MessageContainer";
import "#admin/ak-interface-admin";

View File

@@ -20,13 +20,13 @@ import { SlottedTemplateResult } from "#elements/types";
import { ModelEnum, ProvidersApi, ProxyMode, ProxyProvider } from "@goauthentik/api";
import MDCaddyStandalone from "~docs/add-secure-apps/providers/proxy/_caddy_standalone.md";
import MDNginxIngress from "~docs/add-secure-apps/providers/proxy/_nginx_ingress.md";
import MDNginxPM from "~docs/add-secure-apps/providers/proxy/_nginx_proxy_manager.md";
import MDNginxStandalone from "~docs/add-secure-apps/providers/proxy/_nginx_standalone.md";
import MDTraefikCompose from "~docs/add-secure-apps/providers/proxy/_traefik_compose.md";
import MDTraefikIngress from "~docs/add-secure-apps/providers/proxy/_traefik_ingress.md";
import MDTraefikStandalone from "~docs/add-secure-apps/providers/proxy/_traefik_standalone.md";
import MDCaddyStandalone from "~docs/add-secure-apps/providers/proxy/_caddy_standalone.mdx";
import MDNginxIngress from "~docs/add-secure-apps/providers/proxy/_nginx_ingress.mdx";
import MDNginxPM from "~docs/add-secure-apps/providers/proxy/_nginx_proxy_manager.mdx";
import MDNginxStandalone from "~docs/add-secure-apps/providers/proxy/_nginx_standalone.mdx";
import MDTraefikCompose from "~docs/add-secure-apps/providers/proxy/_traefik_compose.mdx";
import MDTraefikIngress from "~docs/add-secure-apps/providers/proxy/_traefik_ingress.mdx";
import MDTraefikStandalone from "~docs/add-secure-apps/providers/proxy/_traefik_standalone.mdx";
import MDHeaderAuthentication from "~docs/add-secure-apps/providers/proxy/header_authentication.mdx";
import { msg } from "@lit/localize";

View File

@@ -1,7 +1,7 @@
import { renderForm } from "./SAMLProviderImportFormForm.js";
import { aki } from "#common/api/client";
import { SentryIgnoredError } from "#common/sentry/index";
import { SentryIgnoredError } from "#common/sentry/error";
import { Form } from "#elements/forms/Form";

View File

@@ -34,7 +34,7 @@ import {
SCIMProvider,
} from "@goauthentik/api";
import MDSCIMProvider from "~docs/add-secure-apps/providers/scim/index.md";
import MDSCIMProvider from "~docs/add-secure-apps/providers/scim/index.mdx";
import { msg } from "@lit/localize";
import { CSSResult, html, nothing, PropertyValues } from "lit";

View File

@@ -24,7 +24,7 @@ import { scheduleCard } from "#components/tasks/scheduleCard";
import { KerberosSource, ModelEnum, SourcesApi } from "@goauthentik/api";
import MDSourceKerberosBrowser from "~docs/users-sources/sources/protocols/kerberos/browser.md";
import MDSourceKerberosBrowser from "~docs/users-sources/sources/protocols/kerberos/browser.mdx";
import { msg } from "@lit/localize";
import { CSSResult, html, nothing } from "lit";

View File

@@ -33,8 +33,12 @@ export class AccountLockdownStageForm extends BaseStageForm<AccountLockdownStage
)}
</span>
<ak-text-input
label=${msg("Stage Name")}
placeholder=${msg("Type a name for this stage...")}
label=${msg("Stage Name", {
id: "stage.name.label",
})}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
required
name="name"
value=${ifPresent(this.instance?.name || "")}

View File

@@ -1,4 +1,5 @@
import "#components/ak-secret-text-input";
import "#components/ak-text-input";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/SearchSelect/index";
@@ -44,14 +45,18 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
"Stage used to configure a duo-based authenticator. This stage should be used for configuration flows.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authenticator type name")}
?required=${false}

View File

@@ -1,4 +1,5 @@
import "#components/ak-secret-text-input";
import "#components/ak-text-input";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/Radio";
@@ -135,14 +136,18 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai
protected override renderForm(): TemplateResult {
return html` <span> ${msg("Stage used to configure an email-based authenticator.")}</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authenticator type name")}
?required=${false}

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/CodeMirror";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
@@ -45,14 +46,18 @@ export class AuthenticatorEndpointGDTCStageForm extends BaseStageForm<Authentica
"Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Google Verified Access API")}">
<div class="pf-c-form">
<ak-form-element-horizontal

View File

@@ -1,4 +1,5 @@
import "#components/ak-switch-input";
import "#components/ak-text-input";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/Radio";
@@ -169,14 +170,18 @@ export class AuthenticatorSMSStageForm extends BaseStageForm<AuthenticatorSMSSta
return html` <span>
${msg("Stage used to configure an SMS-based TOTP authenticator.")}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authenticator type name")}
?required=${false}

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/SearchSelect/index";
@@ -39,14 +40,18 @@ export class AuthenticatorStaticStageForm extends BaseStageForm<AuthenticatorSta
"Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authenticator type name")}
?required=${false}

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/SearchSelect/index";
@@ -40,14 +41,18 @@ export class AuthenticatorTOTPStageForm extends BaseStageForm<AuthenticatorTOTPS
"Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator).",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authenticator type name")}
?required=${false}

View File

@@ -107,7 +107,9 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
<ak-text-input
autofocus
label=${msg("Stage Name")}
label=${msg("Stage Name", {
id: "stage.name.label",
})}
placeholder=${msg("Type a stage name...")}
required
name="name"

View File

@@ -1,4 +1,5 @@
import "#components/ak-number-input";
import "#components/ak-text-input";
import "#elements/ak-dual-select/ak-dual-select-provider";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/Radio";
@@ -65,14 +66,18 @@ export class AuthenticatorWebAuthnStageForm extends BaseStageForm<AuthenticatorW
"Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authenticator type name")}
?required=${false}

View File

@@ -356,7 +356,9 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
)}
</header>
<ak-text-input
label=${msg("Stage Name")}
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value="${this.instance?.name || "my-captcha-stage"}"

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
import "#elements/utils/TimeDeltaHelp";
@@ -47,14 +48,18 @@ export class ConsentStageForm extends BaseStageForm<ConsentStage> {
"Prompt for the user's consent. The consent can either be permanent or expire in a defined amount of time.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/HorizontalFormElement";
import { aki } from "#common/api/client";
@@ -28,14 +29,18 @@ export class DenyStageForm extends BaseStageForm<DenyStage> {
"Statically deny the flow. To use this stage effectively, disable *Evaluate when flow is planned* on the respective binding.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal label=${msg("Deny message")} name="denyMessage">

View File

@@ -1,4 +1,5 @@
import "#components/ak-switch-input";
import "#components/ak-text-input";
import "#elements/forms/HorizontalFormElement";
import { aki } from "#common/api/client";
@@ -10,7 +11,6 @@ import { DummyStage, StagesApi } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-stage-dummy-form")
export class DummyStageForm extends BaseStageForm<DummyStage> {
@@ -28,14 +28,18 @@ export class DummyStageForm extends BaseStageForm<DummyStage> {
"Dummy stage used for testing. Shows a simple continue button and always passes.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-switch-input
name="throwError"
label=${msg("Throw error?")}

View File

@@ -1,4 +1,5 @@
import "#components/ak-secret-text-input";
import "#components/ak-text-input";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
import "#elements/utils/TimeDeltaHelp";
@@ -124,14 +125,18 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
"Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-switch-input

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/Radio";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/SearchSelect/index";
@@ -19,7 +20,6 @@ import {
import { msg } from "@lit/localize";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-endpoints-stage-form")
export class EndpointStageForm extends BaseStageForm<EndpointStage> {
@@ -35,14 +35,18 @@ export class EndpointStageForm extends BaseStageForm<EndpointStage> {
return html` <span>
${msg("Stage which associates the currently used device with the current session.")}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal label=${msg("Connector")} required name="connector">

View File

@@ -1,5 +1,6 @@
import "#admin/common/ak-flow-search/ak-flow-search";
import "#components/ak-switch-input";
import "#components/ak-text-input";
import "#elements/ak-checkbox-group/ak-checkbox-group";
import "#elements/ak-dual-select/ak-dual-select-dynamic-selected-provider";
import "#elements/forms/FormGroup";
@@ -29,7 +30,6 @@ import {
import { msg } from "@lit/localize";
import { css, html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-stage-identification-form")
export class IdentificationStageForm extends BaseStageForm<IdentificationStage> {
@@ -68,14 +68,18 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
return html`<span>
${msg("Let the user identify themselves with their username or Email address.")}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal name="userFields">

View File

@@ -28,7 +28,9 @@ export class InvitationStageForm extends BaseStageForm<InvitationStage> {
protected override renderForm(): TemplateResult {
return html`<ak-text-input
label=${msg("Stage Name")}
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value="${this.instance?.name || ""}"

View File

@@ -1,4 +1,5 @@
import "#admin/common/ak-crypto-certificate-search";
import "#components/ak-text-input";
import "#elements/ak-dual-select/ak-dual-select-dynamic-selected-provider";
import "#elements/ak-dual-select/ak-dual-select-provider";
import "#elements/forms/FormGroup";
@@ -21,7 +22,6 @@ import {
import { msg } from "@lit/localize";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-stage-mtls-form")
export class MTLSStageForm extends BaseStageForm<MutualTLSStage> {
@@ -36,14 +36,18 @@ export class MTLSStageForm extends BaseStageForm<MutualTLSStage> {
protected override renderForm(): TemplateResult {
return html`
<span> ${msg("Client-certificate/mTLS authentication/enrollment.")}</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/ak-checkbox-group/ak-checkbox-group";
import "#components/ak-switch-input";
import "#elements/forms/FormGroup";
@@ -69,14 +70,18 @@ export class PasswordStageForm extends BaseStageForm<PasswordStage> {
return html` <span>
${msg("Validate the user's password against the selected backend(s).")}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name || ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal required name="backends">

View File

@@ -1,6 +1,7 @@
import "#admin/stages/prompt/PromptForm";
import "#elements/ak-dual-select/ak-dual-select-dynamic-selected-provider";
import "#elements/forms/FormGroup";
import "#components/ak-text-input";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/ModalForm";
@@ -21,7 +22,6 @@ import { PromptStage, StagesApi } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html, nothing, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-stage-prompt-form")
export class PromptStageForm extends BaseStageForm<PromptStage> {
@@ -34,19 +34,25 @@ export class PromptStageForm extends BaseStageForm<PromptStage> {
};
protected override renderForm(): TemplateResult {
return html` <span>
return html`<p>
${msg(
"Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.",
html`Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<code class="ak-m-code-emphasis">prompt_data</code> variable.`,
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</p>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal label=${msg("Fields")} required name="fields">

View File

@@ -1,4 +1,5 @@
import "#components/ak-switch-input";
import "#components/ak-text-input";
import "#elements/forms/SearchSelect/ak-search-select";
import "#elements/forms/HorizontalFormElement";
@@ -54,14 +55,18 @@ export class RedirectStageForm extends BaseStageForm<RedirectStage> {
"Redirect the user to a static URL or another flow, optionally with all gathered context.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">

View File

@@ -1,4 +1,5 @@
import "#elements/ak-checkbox-group/ak-checkbox-group";
import "#components/ak-text-input";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/SearchSelect/index";
import "#elements/utils/TimeDeltaHelp";
@@ -38,14 +39,18 @@ export class SourceStageForm extends BaseStageForm<SourceStage> {
"Inject an OAuth or SAML Source into the flow execution. This allows for additional user verification, or to dynamically access different sources for different user identifiers (username, email address, etc).",
)}</span
>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-element-horizontal label=${msg("Source")} required name="source">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Source[]> => {

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/HorizontalFormElement";
import { aki } from "#common/api/client";
@@ -9,7 +10,6 @@ import { StagesApi, UserDeleteStage } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-stage-user-delete-form")
export class UserDeleteStageForm extends BaseStageForm<UserDeleteStage> {
@@ -27,14 +27,18 @@ export class UserDeleteStageForm extends BaseStageForm<UserDeleteStage> {
"Delete the currently pending user. CAUTION, this stage does not ask for confirmation. Use a consent stage to ensure the user is aware of their actions.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>`;
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>`;
}
}

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/Radio";
import "#components/ak-switch-input";
import "#elements/Alert";
@@ -27,14 +28,18 @@ export class UserLoginStageForm extends BaseStageForm<UserLoginStage> {
protected override renderForm(): TemplateResult {
return html` <span>${msg("Log the currently pending user in.")}</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-element-horizontal

View File

@@ -1,3 +1,4 @@
import "#components/ak-text-input";
import "#elements/forms/HorizontalFormElement";
import { aki } from "#common/api/client";
@@ -9,7 +10,6 @@ import { StagesApi, UserLogoutStage } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-stage-user-logout-form")
export class UserLogoutStageForm extends BaseStageForm<UserLogoutStage> {
@@ -23,14 +23,18 @@ export class UserLogoutStageForm extends BaseStageForm<UserLogoutStage> {
protected override renderForm(): TemplateResult {
return html` <span>${msg("Remove the user from the current session.")}</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>`;
<ak-text-input
label=${msg("Stage Name", {
id: "stage.name.label",
})}
required
name="name"
value=${this.instance?.name || ""}
placeholder=${msg("Type a name for this stage...", {
id: "stage.name.placeholder",
})}
?autofocus=${!this.instance}
></ak-text-input>`;
}
}

View File

@@ -48,7 +48,9 @@ export class UserWriteStageForm extends BaseStageForm<UserWriteStage> {
</div>
<ak-text-input
autofocus
label=${msg("Stage Name")}
label=${msg("Stage Name", {
id: "stage.name.label",
})}
placeholder=${msg("Type a stage name...")}
required
name="name"

View File

@@ -3,7 +3,7 @@ import "#elements/forms/DeleteBulkForm";
import { aki } from "#common/api/client";
import { createPaginatedResponse } from "#common/api/responses";
import { deviceTypeName } from "#common/labels";
import { SentryIgnoredError } from "#common/sentry/index";
import { SentryIgnoredError } from "#common/sentry/error";
import { PaginatedResponse, Table, TableColumn, Timestamp } from "#elements/table/Table";
import { SlottedTemplateResult } from "#elements/types";

View File

@@ -1,4 +1,4 @@
import { SentryIgnoredError } from "#common/sentry/index";
import { SentryIgnoredError } from "#common/sentry/error";
export interface PlexPinResponse {
// Only has the fields we care about

View File

@@ -0,0 +1,17 @@
import { getClient } from "@sentry/browser";
import { describe, expect, it } from "vitest";
describe("sentry/apply", () => {
it("boots without a server-injected global", async () => {
// `globalAK` falls back to `ConfigFromJSON({ capabilities: [] })` when the
// server didn't inject `window.authentik`, and `errorReporting` comes back
// undefined despite `Config` typing it as required. Reading through it
// unguarded throws here — in the first import of every entrypoint, taking
// the whole interface down rather than just Sentry.
delete (window as Partial<Window & { authentik: unknown }>).authentik;
await expect(import("#common/sentry/apply")).resolves.toBeDefined();
expect(getClient(), "Sentry stays uninitialized with no configuration").toBeUndefined();
});
});

View File

@@ -0,0 +1,62 @@
/**
* @file Initializes Sentry as an import side effect.
*
* Imported first from each interface entrypoint so reporting is live before the
* element modules evaluate and custom elements register — errors thrown during
* that window used to escape, because initialization ran in an element
* constructor.
*
* The enable/disable policy is {@linkcode isSentryEnabled}, which is a pure
* function so it can be tested without a browser.
*/
import { globalAK } from "#common/global";
import {
DEFAULT_SENTRY_BROWSER_OPTIONS,
isSentryEnabled,
setSentryCapabilities,
setSentryInterface,
} from "#common/sentry/utils";
import { readInterfaceRouteParam } from "#elements/router/utils";
import { ConsoleLogger } from "#logger/browser";
import { CapabilitiesEnum } from "@goauthentik/api";
import { browserTracingIntegration, init, spotlightBrowserIntegration } from "@sentry/browser";
import { type Integration } from "@sentry/core/browser";
const { errorReporting, capabilities } = globalAK().config;
const debug = capabilities.includes(CapabilitiesEnum.CanDebug);
if (isSentryEnabled({ errorReporting, debug, search: window.location.search })) {
const logger = ConsoleLogger.prefix("sentry");
const integrations: Integration[] = [
browserTracingIntegration({
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
instrumentNavigation: false,
instrumentPageLoad: false,
traceFetch: false,
}),
];
if (debug) {
logger.debug("Enabled Spotlight");
integrations.push(spotlightBrowserIntegration());
}
init({
...DEFAULT_SENTRY_BROWSER_OPTIONS,
integrations,
tracePropagationTargets: [window.location.origin],
dsn: errorReporting?.sentryDsn,
tracesSampleRate: debug ? 1.0 : errorReporting?.tracesSampleRate,
environment: errorReporting?.environment,
});
setSentryCapabilities(capabilities);
setSentryInterface(readInterfaceRouteParam());
}

View File

@@ -0,0 +1,16 @@
/**
* @file An error that opts out of Sentry reporting.
*
* Deliberately import-free. This module is pulled in by form and API helpers
* across all three interfaces, none of which want the Sentry SDK or the
* generated API client dragged along with the class.
*/
/**
* A generic error that can be thrown without triggering Sentry's reporting.
*
* @see {@linkcode beforeSend} in `sentry/utils.ts`, which drops these events.
*
* @category Sentry
*/
export class SentryIgnoredError extends Error {}

View File

@@ -1,107 +0,0 @@
import { globalAK } from "#common/global";
import { readInterfaceRouteParam } from "#elements/router/utils";
import { ConsoleLogger } from "#logger/browser";
import { CapabilitiesEnum, ResponseError } from "@goauthentik/api";
import {
browserTracingIntegration,
ErrorEvent,
EventHint,
init,
setTag,
spotlightBrowserIntegration,
} from "@sentry/browser";
import { type Integration } from "@sentry/core";
/**
* A generic error that can be thrown without triggering Sentry's reporting.
*/
export class SentryIgnoredError extends Error {}
export const TAG_SENTRY_COMPONENT = "authentik.component";
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
function beforeSend(
event: ErrorEvent,
hint: EventHint,
): ErrorEvent | PromiseLike<ErrorEvent | null> | null {
if (!hint) {
return event;
}
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
if (
hint.originalException instanceof ResponseError ||
hint.originalException instanceof DOMException
) {
return null;
}
return event;
}
export function configureSentry(): void {
const cfg = globalAK().config;
const debug = cfg.capabilities.includes(CapabilitiesEnum.CanDebug);
if (!cfg.errorReporting?.enabled && !debug) {
return;
}
const logger = ConsoleLogger.prefix("sentry");
const integrations: Integration[] =
process.env.NODE_ENV === "production"
? [
browserTracingIntegration({
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
instrumentNavigation: false,
instrumentPageLoad: false,
traceFetch: false,
}),
]
: [];
if (debug) {
logger.debug("Enabled Spotlight");
integrations.push(spotlightBrowserIntegration());
}
init({
enabled: process.env.NODE_ENV !== "production",
dsn: cfg.errorReporting.sentryDsn,
ignoreErrors: [
/network/gi,
/fetch/gi,
/module/gi,
// Error on edge on ios,
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
/instantSearchSDKJSBridgeClearHighlight/gi,
// Seems to be an issue in Safari and Firefox
/MutationObserver.observe/gi,
/NS_ERROR_FAILURE/gi,
],
release:
process.env.NODE_ENV === "production"
? `authentik@${import.meta.env.AK_VERSION}`
: undefined,
integrations,
tracePropagationTargets: [window.location.origin],
tracesSampleRate: debug ? 1.0 : cfg.errorReporting.tracesSampleRate,
environment: cfg.errorReporting.environment,
beforeSend,
});
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
if (window.location.pathname.includes("if/")) {
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
}
logger.debug("Initialized!");
}

View File

@@ -0,0 +1,138 @@
import { SentryIgnoredError } from "#common/sentry/error";
import { RouteInterfaceName } from "#elements/router/utils";
import { CapabilitiesEnum, type ErrorReportingConfig, ResponseError } from "@goauthentik/api";
import { BrowserOptions, ErrorEvent, EventHint, setTag } from "@sentry/browser";
/**
* Query parameter that turns Sentry off for a single page load.
*/
export const DISABLE_SENTRY_PARAM = "disable-sentry";
/**
* The configuration needed to determine whether Sentry should report for this page load.
*
* @see {@linkcode isSentryEnabled}
*/
export interface SentrySetupOptions {
/**
* The deployment's error-reporting configuration.
*
* Optional because `Config` types it as required while
* `ErrorReportingConfigFromJSON` passes a missing value straight through —
* it is absent whenever the server didn't inject `window.authentik`.
*/
errorReporting?: ErrorReportingConfig;
/**
* Whether the instance reports the `CanDebug` capability.
*/
debug: boolean;
/**
* The current query string, i.e. `window.location.search`.
*/
search: string;
/**
* Whether this is a production build. Defaults to the build-time environment.
*/
production?: boolean;
}
/**
* Whether Sentry should report for this page load.
*
* The administrator's `errorReporting.enabled` setting decides, in every
* environment — a deployment that turns error reporting on expects to receive
* errors. `CanDebug` enables it on its own, which is what activates Spotlight.
*
* Development additionally honors `?disable-sentry`, so a noisy local session
* can opt out for one load without a rebuild.
*
* @category Sentry
*/
export function isSentryEnabled({
errorReporting,
debug,
search,
production = process.env.NODE_ENV === "production",
}: SentrySetupOptions): boolean {
if (!errorReporting?.enabled && !debug) return false;
if (production) return true;
const params = new URLSearchParams(search);
return !params.has(DISABLE_SENTRY_PARAM);
}
/**
* A `beforeSend` callback that ignores certain errors.
*
* @category Sentry
*/
export function beforeSend(
event: ErrorEvent,
hint: EventHint,
): ErrorEvent | PromiseLike<ErrorEvent | null> | null {
if (!hint) {
return event;
}
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
if (
hint.originalException instanceof ResponseError ||
hint.originalException instanceof DOMException
) {
return null;
}
return event;
}
/**
* Include the given capabilities in Sentry events.
*
* @category Sentry
*/
export function setSentryCapabilities(capabilities: CapabilitiesEnum[]): void {
setTag("authentik.capabilities", capabilities.join(","));
}
/**
* Include the given route interface in Sentry events.
*
* @category Sentry
*/
export function setSentryInterface(interfaceName: RouteInterfaceName) {
setTag("authentik.component", `web/${interfaceName}`);
}
/**
* Default Sentry options for the browser.
*
* Free of browser globals at module scope, so the policy this module also
* exports stays importable outside a document.
*
* @category Sentry
*/
export const DEFAULT_SENTRY_BROWSER_OPTIONS = {
ignoreErrors: [
/network/gi,
/fetch/gi,
/module/gi,
// Error on edge on ios,
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
/instantSearchSDKJSBridgeClearHighlight/gi,
// Seems to be an issue in Safari and Firefox
/MutationObserver.observe/gi,
/NS_ERROR_FAILURE/gi,
],
release:
process.env.NODE_ENV === "production"
? `authentik@${import.meta.env.AK_VERSION}`
: undefined,
beforeSend,
} as const satisfies BrowserOptions;

View File

@@ -0,0 +1,87 @@
import { DISABLE_SENTRY_PARAM, isSentryEnabled } from "./utils.js";
import { type ErrorReportingConfig } from "@goauthentik/api";
import { describe, expect, it } from "vitest";
const reporting = (enabled: boolean): ErrorReportingConfig => ({ enabled }) as ErrorReportingConfig;
describe("isSentryEnabled", () => {
it("is false when the administrator has error reporting off", () => {
expect(
isSentryEnabled({ errorReporting: reporting(false), debug: false, search: "" }),
).toBe(false);
});
it("is true when the administrator has error reporting on", () => {
expect(isSentryEnabled({ errorReporting: reporting(true), debug: false, search: "" })).toBe(
true,
);
});
it("is true for a debug instance even with error reporting off", () => {
// `CanDebug` is what activates Spotlight; it must not depend on the
// administrator's reporting setting.
expect(isSentryEnabled({ errorReporting: reporting(false), debug: true, search: "" })).toBe(
true,
);
});
it("is false when the config is absent entirely", () => {
// `Config` types `errorReporting` as required, but the server may not
// have injected `window.authentik` at all.
expect(isSentryEnabled({ debug: false, search: "" })).toBe(false);
});
describe("in production", () => {
const production = true;
it("ignores the disable parameter", () => {
expect(
isSentryEnabled({
errorReporting: reporting(true),
debug: false,
search: `?${DISABLE_SENTRY_PARAM}`,
production,
}),
).toBe(true);
});
});
describe("in development", () => {
const production = false;
it("honors the disable parameter", () => {
expect(
isSentryEnabled({
errorReporting: reporting(true),
debug: false,
search: `?${DISABLE_SENTRY_PARAM}`,
production,
}),
).toBe(false);
});
it("honors the disable parameter alongside other parameters", () => {
expect(
isSentryEnabled({
errorReporting: reporting(true),
debug: true,
search: `?q=authentik&${DISABLE_SENTRY_PARAM}=1`,
production,
}),
).toBe(false);
});
it("reports when the parameter is absent", () => {
expect(
isSentryEnabled({
errorReporting: reporting(true),
debug: false,
search: "?q=authentik",
production,
}),
).toBe(true);
});
});
});

View File

@@ -1,4 +1,4 @@
import { SentryIgnoredError } from "#common/sentry/index";
import { SentryIgnoredError } from "#common/sentry/error";
export class PreventFormSubmit extends SentryIgnoredError {
// Stub class which can be returned by form elements to prevent the form from submitting

View File

@@ -12,7 +12,6 @@ import Styles from "./FlowExecutor.css" with { type: "bundled-text" };
import { aki } from "#common/api/client";
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
import { globalAK } from "#common/global";
import { configureSentry } from "#common/sentry/index";
import { applyBackgroundImageProperty, resolveThemedUrl } from "#common/theme";
import { Interface } from "#elements/Interface";
@@ -152,7 +151,6 @@ export class FlowExecutor extends WithBrandConfig(Interface) implements StageHos
//#region Lifecycle
constructor() {
configureSentry();
super();
this.#api = aki(FlowsApi);
this.addController(this.#flowIframeMessageController);

View File

@@ -1,3 +1,4 @@
import "#common/sentry/apply";
import "#elements/messages/MessageContainer";
import "#elements/ak-drawer/ak-drawer";
import "#flow/FlowExecutor";

View File

@@ -1,5 +1,4 @@
fieldset[name="login-sources"],
ak-stage-identification.style-scope fieldset[name="login-sources"] {
fieldset[name="login-sources"] {
--ak-c-login-sources-padding-inline: var(--pf-global--spacer--xl);
flex: 1 1 auto;

View File

@@ -1,6 +1,6 @@
// sort-imports-ignore
import "@webcomponents/webcomponentsjs";
import "lit/polyfill-support.js";
import "lit-element/polyfill-support.js";
import "./custom-elements-get-name.js";
import "core-js/actual";
import "@formatjs/intl-listformat/polyfill.js";

View File

@@ -26,6 +26,15 @@
text-transform: capitalize;
}
.ak-m-code-emphasis {
display: inline-block;
vertical-align: baseline;
padding-inline: var(--ak-m-code-emphasis--PaddingInline, 0.25rem);
font-weight: var(--ak-m-code-emphasis--FontWeight, 500);
background: var(--pf-global--BackgroundColor--150);
border-radius: var(--pf-global--BorderRadius--sm);
}
::placeholder {
font-style: italic;
}

View File

@@ -6,7 +6,6 @@ import "#elements/router/RouterOutlet";
import "#components/ak-nav-tabs";
import { globalAK } from "#common/global";
import { configureSentry } from "#common/sentry/index";
import { isGuest } from "#common/users";
import { WebsocketClient } from "#common/ws/WebSocketClient";
@@ -83,8 +82,6 @@ class UserInterface extends WithLicenseSummary(
//#region Lifecycle
constructor() {
configureSentry();
super();
WebsocketClient.connect();

View File

@@ -1,3 +1,4 @@
import "#common/sentry/apply";
import "#elements/messages/MessageContainer";
import "#user/ak-interface-user";

View File

@@ -1,7 +1,7 @@
import "#elements/forms/HorizontalFormElement";
import { aki } from "#common/api/client";
import { SentryIgnoredError } from "#common/sentry/index";
import { SentryIgnoredError } from "#common/sentry/error";
import { ModelForm } from "#elements/forms/ModelForm";

View File

@@ -11,7 +11,7 @@ import { AndNext } from "#common/api/config";
import { createPaginatedResponse } from "#common/api/responses";
import { globalAK } from "#common/global";
import { deviceTypeName } from "#common/labels";
import { SentryIgnoredError } from "#common/sentry/index";
import { SentryIgnoredError } from "#common/sentry/error";
import { PaginatedResponse, Table, TableColumn, Timestamp } from "#elements/table/Table";
import { SlottedTemplateResult } from "#elements/types";

10
web/types/mdx.d.ts vendored
View File

@@ -1,18 +1,10 @@
/**
* @file Provides types for ESBuild "virtual modules" generated from
* Markdown / MDX files. The bundler's `mdx-plugin` compiles these to
* MDX files. The bundler's `mdx-plugin` compiles these to
* HTML at build time and emits a JSON envelope; importing the file
* yields the URL of that JSON envelope.
*/
declare module "~docs/*.md" {
/**
* URL of the JSON envelope emitted for the imported file.
*/
const url: string;
export default url;
}
declare module "~docs/*.mdx" {
/**
* URL of the JSON envelope emitted for the imported file.

View File

@@ -6656,10 +6656,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Fields</target>
@@ -9585,10 +9581,6 @@ Bindings to groups/users are checked against the user of the event.</target>
<source>Paste your CAPTCHA secret key...</source>
<target>Paste your CAPTCHA secret key...</target>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
<target>Stage Name</target>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
<target>Type a stage name...</target>
@@ -11761,10 +11753,6 @@ Bindings to groups/users are checked against the user of the event.</target>
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
<target>تُنفِّذ هذه المرحلة إجراءات قفل الحساب على مستخدم مستهدف. قم بتكوين الإجراءات التي تريد تنفيذها عند تشغيل هذه المرحلة.</target>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
<target>اكتب اسماً لهذه المرحلة...</target>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
<target>تعطيل المستخدم</target>
@@ -13203,6 +13191,17 @@ Bindings to groups/users are checked against the user of the event.</target>
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6655,10 +6655,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", от тип <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Показва произволни входни полета на потребителя, например по време на регистрация. Данните се записват в контекста на потока под променливата 'prompt_data'.</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Полета</target>
@@ -9584,10 +9580,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Paste your CAPTCHA secret key...</source>
<target>Поставете тайния си CAPTCHA ключ...</target>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
<target>Име на стъпка</target>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
<target>Въведете име на стъпка...</target>
@@ -11728,9 +11720,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -13049,6 +13038,17 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6467,10 +6467,6 @@ neprojde, když jedna nebo obě z vybraných možností jsou rovny nebo nad prah
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", typu <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Zobrazit uživateli libovolná vstupní pole, například během registrace. Data jsou uložena v kontextu toku pod proměnnou 'prompt_data'.</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Pole</target>
@@ -9139,9 +9135,6 @@ Vazby na skupiny/uživatele jsou kontrolovány vůči uživateli události.</tar
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -10775,9 +10768,6 @@ Vazby na skupiny/uživatele jsou kontrolovány vůči uživateli události.</tar
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -12096,6 +12086,17 @@ Vazby na skupiny/uživatele jsou kontrolovány vůči uživateli události.</tar
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6493,10 +6493,6 @@ Beim Erstellen eines festen Auswahlfelds aktiviere „Als Ausdruck interpretiere
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, vom typ <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Beliebige Eingabefelder für den Benutzer anzeigen, zum Beispiel während des Enrollments. Die Daten werden im Flow-Kontext unter der Variablen `prompt_data` gespeichert.</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Felder</target>
@@ -9171,9 +9167,6 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -10807,9 +10800,6 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -12128,6 +12118,17 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4991,9 +4991,6 @@ doesn't pass when either or both of the selected options are equal or above the
<trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
</trans-unit>
@@ -7183,9 +7180,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -8819,9 +8813,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -10140,6 +10131,17 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6431,10 +6431,6 @@ El valor de este campo se compara con el atributo de pertenencia del usuario.</t
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, of type <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Muestra campos de entrada arbitrarios al usuario, por ejemplo, durante la inscripción. Los datos se guardan en el contexto del flujo bajo la variable 'prompt_data'.</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Campos</target>
@@ -9098,9 +9094,6 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -10734,9 +10727,6 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -12055,6 +12045,17 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6607,10 +6607,6 @@ läpäisy estyy kun jompi kumpi tai molemmat vaihtoehdot ylittävät raja-arvon.
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, tyyppiä <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Näytä vapaavalintaisia kenttiä käyttäjälle, esimerkiksi rekisteröitymisen yhteydessä. Tiedot tallennetaan prosessin kontekstiin 'prompt_data'-muuttujaan.</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Kentät</target>
@@ -9336,9 +9332,6 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -10972,9 +10965,6 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -12293,6 +12283,17 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6597,10 +6597,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, de type <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Afficher des champs de saisie arbitraires à l'utilisateur, par exemple pendant l'inscription. Les données sont enregistrées dans le contexte du flux sous la variable "prompt_data".</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Champs</target>
@@ -9325,9 +9321,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -10961,9 +10954,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -12282,6 +12272,17 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6387,10 +6387,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, del tipo <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Mostra campi di input arbitrari all'utente, ad esempio durante l'iscrizione. I dati vengono salvati nel contesto di flusso nell'ambito della variabile "prompt_data".</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>Campi</target>
@@ -9047,9 +9043,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -10683,9 +10676,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -12004,6 +11994,17 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -6604,10 +6604,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/>(「<x id="1" equiv-text="${p.fieldKey}"/>」、タイプ <x id="2" equiv-text="${p.type}"/></target>
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>ユーザーに任意の入力フィールドを表示します登録中。データはフローコンテキストに「prompt_data」変数として保存されます。</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
<source>Fields</source>
<target>フィールド</target>
@@ -9325,9 +9321,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s4cbbb47ef050cd0e">
<source>Paste your CAPTCHA secret key...</source>
</trans-unit>
<trans-unit id="s896c7fc0f9277a74">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="s1baea28d44c34fef">
<source>Type a stage name...</source>
</trans-unit>
@@ -10961,9 +10954,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s5ba577d0cda2bad2">
<source>This stage executes account lockdown actions on a target user. Configure which actions to perform when this stage runs.</source>
</trans-unit>
<trans-unit id="sc2908875e6774352">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="scf443deca2f466b6">
<source>Deactivate user</source>
</trans-unit>
@@ -12282,6 +12272,17 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="stages.source.match-failure.missing-property.label">
<source>Missing property</source>
</trans-unit>
<trans-unit id="stage.name.label">
<source>Stage Name</source>
</trans-unit>
<trans-unit id="stage.name.placeholder">
<source>Type a name for this stage...</source>
</trans-unit>
<trans-unit id="hc50d8e6b4c9f79f1">
<source>Show arbitrary input fields to the user, for example during enrollment.
Data is saved in the flow context under the
<x id="0" equiv-text="&lt;code class=&quot;ak-m-code-emphasis&quot;&gt;"/>prompt_data<x id="1" equiv-text="&lt;/code&gt;"/> variable.</source>
</trans-unit>
</body>
</file>
</xliff>

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