Merge branch 'main' into web/stage--identification--change-login-to-log-in

This commit is contained in:
Dewi Roberts
2026-02-24 09:20:13 +00:00
committed by GitHub
101 changed files with 2796 additions and 2391 deletions

View File

@@ -6,6 +6,10 @@ on:
schedule:
# Every night at 3am
- cron: "0 3 * * *"
pull_request:
paths:
# Needs to refer to itself
- .github/workflows/ci-main-daily.yml
jobs:
test-container:
@@ -15,14 +19,14 @@ jobs:
matrix:
version:
- docs
- version-2025-4
- version-2025-2
- version-2025-12
- version-2025-10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- run: |
current="$(pwd)"
dir="/tmp/authentik/${{ matrix.version }}"
mkdir -p $dir
cd $dir
wget https://${{ matrix.version }}.goauthentik.io/compose.yml
${current}/scripts/test_docker.sh
mkdir -p "${dir}/lifecycle/container"
cd "${dir}"
wget "https://${{ matrix.version }}.goauthentik.io/docker-compose.yml" -O "${dir}/lifecycle/container/compose.yml"
"${current}/scripts/test_docker.sh"

View File

@@ -15,6 +15,7 @@ from django.core.cache import cache
from django.db.models.query import QuerySet
from django.utils.timezone import now
from jwt import PyJWTError, decode, get_unverified_header
from jwt.algorithms import ECAlgorithm
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
ChoiceField,
@@ -109,7 +110,15 @@ class LicenseKey:
intermediate.verify_directly_issued_by(get_licensing_key())
except InvalidSignature, TypeError, ValueError, Error:
raise ValidationError("Unable to verify license") from None
_validate_curve_original = ECAlgorithm._validate_curve
try:
# authentik's license used to be generated with `algorithm="ES512"` and signed with
# a key of curve `secp384r1`. Starting with version 2.11.0, pyjwt enforces the spec, see
# https://github.com/jpadilla/pyjwt/commit/5b8622773358e56d3d3c0a9acf404809ff34433a
# New licenses are generated with `algorithm="ES384"` and signed with `secp384r1`.
# The last license will run out by March 2027.
# TODO: remove this in March 2027.
ECAlgorithm._validate_curve = lambda *_: True
body = from_dict(
LicenseKey,
decode(
@@ -125,6 +134,8 @@ class LicenseKey:
if unverified["aud"] != get_license_aud():
raise ValidationError("Invalid Install ID in license") from None
raise ValidationError("Unable to verify license") from None
finally:
ECAlgorithm._validate_curve = _validate_curve_original
return body
@staticmethod

View File

@@ -1,11 +1,11 @@
from datetime import date
from datetime import datetime
from django.db.models import BooleanField as ModelBooleanField
from django.db.models import Case, Q, Value, When
from django_filters.rest_framework import BooleanFilter, FilterSet
from drf_spectacular.utils import extend_schema, extend_schema_field
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.fields import DateField, IntegerField, SerializerMethodField
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.mixins import CreateModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
@@ -21,6 +21,7 @@ from authentik.enterprise.lifecycle.utils import (
ReviewerUserSerializer,
admin_link_for_model,
parse_content_type,
start_of_day,
)
from authentik.lib.utils.time import timedelta_from_string
@@ -67,13 +68,13 @@ class LifecycleIterationSerializer(EnterpriseRequiredMixin, ModelSerializer):
def get_object_admin_url(self, iteration: LifecycleIteration) -> str:
return admin_link_for_model(iteration.object)
@extend_schema_field(DateField())
def get_grace_period_end(self, iteration: LifecycleIteration) -> date:
return iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
def get_grace_period_end(self, iteration: LifecycleIteration) -> datetime:
return start_of_day(
iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
)
@extend_schema_field(DateField())
def get_next_review_date(self, iteration: LifecycleIteration):
return iteration.opened_on + timedelta_from_string(iteration.rule.interval)
def get_next_review_date(self, iteration: LifecycleIteration) -> datetime:
return start_of_day(iteration.opened_on + timedelta_from_string(iteration.rule.interval))
def get_user_can_review(self, iteration: LifecycleIteration) -> bool:
return iteration.user_can_review(self.context["request"].user)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.11 on 2026-02-13 09:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_lifecycle", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="lifecycleiteration",
name="opened_on",
field=models.DateTimeField(auto_now_add=True),
),
]

View File

@@ -1,3 +1,4 @@
from datetime import timedelta
from uuid import uuid4
from django.contrib.contenttypes.fields import GenericForeignKey
@@ -13,7 +14,7 @@ from rest_framework.serializers import BaseSerializer
from authentik.blueprints.models import ManagedModel
from authentik.core.models import Group, User
from authentik.enterprise.lifecycle.utils import link_for_model
from authentik.enterprise.lifecycle.utils import link_for_model, start_of_day
from authentik.events.models import Event, EventAction, NotificationSeverity, NotificationTransport
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
@@ -98,7 +99,9 @@ class LifecycleRule(SerializerModel):
def _get_newly_overdue_iterations(self) -> QuerySet[LifecycleIteration]:
return self.lifecycleiteration_set.filter(
opened_on__lte=timezone.now() - timedelta_from_string(self.grace_period),
opened_on__lt=start_of_day(
timezone.now() + timedelta(days=1) - timedelta_from_string(self.grace_period)
),
state=ReviewState.PENDING,
)
@@ -106,7 +109,9 @@ class LifecycleRule(SerializerModel):
recent_iteration_ids = LifecycleIteration.objects.filter(
content_type=self.content_type,
object_id__isnull=False,
opened_on__gte=timezone.now() - timedelta_from_string(self.interval),
opened_on__gte=start_of_day(
timezone.now() + timedelta(days=1) - timedelta_from_string(self.interval)
),
).values_list(Cast("object_id", output_field=self._get_pk_field()), flat=True)
return self.get_objects().exclude(pk__in=recent_iteration_ids)
@@ -186,7 +191,7 @@ class LifecycleIteration(SerializerModel, ManagedModel):
rule = models.ForeignKey(LifecycleRule, null=True, on_delete=models.SET_NULL)
state = models.CharField(max_length=10, choices=ReviewState, default=ReviewState.PENDING)
opened_on = models.DateField(auto_now_add=True)
opened_on = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [models.Index(fields=["content_type", "opened_on"])]

View File

@@ -1,3 +1,4 @@
import datetime as dt
from datetime import timedelta
from unittest.mock import patch
@@ -319,7 +320,7 @@ class TestLifecycleModels(TestCase):
content_type=content_type, object_id=str(app_one.pk), rule=rule_overdue
)
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=(timezone.now().date() - timedelta(days=20))
opened_on=(timezone.now() - timedelta(days=20))
)
# Apply again to trigger overdue logic
@@ -383,7 +384,7 @@ class TestLifecycleModels(TestCase):
content_type=content_type, object_id=str(app_overdue.pk), rule=rule_overdue
)
LifecycleIteration.objects.filter(pk=overdue_iteration.pk).update(
opened_on=(timezone.now().date() - timedelta(days=20))
opened_on=(timezone.now() - timedelta(days=20))
)
# Apply overdue rule to mark iteration as overdue
@@ -667,3 +668,178 @@ class TestLifecycleModels(TestCase):
reviewers = list(rule.get_reviewers())
self.assertIn(explicit_reviewer, reviewers)
self.assertIn(group_member, reviewers)
class TestLifecycleDateBoundaries(TestCase):
"""Verify that start_of_day normalization ensures correct overdue/due
detection regardless of exact task execution time within a day.
The daily task may run at any point during the day. The start_of_day
normalization in _get_newly_overdue_iterations and _get_newly_due_objects
ensures that the boundary is always at midnight, so millisecond variations
in task execution time do not affect results."""
def _create_rule_and_iteration(self, grace_period="days=1", interval="days=365"):
app = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app.pk),
interval=interval,
grace_period=grace_period,
)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app.pk), rule=rule
)
return app, rule, iteration
def test_overdue_iteration_opened_yesterday(self):
"""grace_period=1 day: iteration opened yesterday at any time is overdue today."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 14, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_not_overdue_iteration_opened_today(self):
"""grace_period=1 day: iteration opened today at any time is NOT overdue."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 15, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_overdue_independent_of_task_execution_time(self):
"""Overdue detection gives the same result whether the task runs at 00:00:01 or 23:59:59."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
opened_on = dt.datetime(2025, 6, 14, 18, 0, 0, tzinfo=dt.UTC)
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
with patch("django.utils.timezone.now", return_value=task_time):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_overdue_boundary_multi_day_grace_period(self):
"""grace_period=30 days: overdue after 30 full days, not after 29."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=30")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
# Opened 30 days ago (May 16), should go overdue
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 16, 12, 0, 0, tzinfo=dt.UTC),
state=ReviewState.PENDING,
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
# Opened 29 days ago (May 17), should NOT go overdue
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 17, 12, 0, 0, tzinfo=dt.UTC),
state=ReviewState.PENDING,
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_due_object_iteration_opened_yesterday(self):
"""interval=1 day: object with iteration opened yesterday is due for a new review."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 14, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(app, list(rule._get_newly_due_objects()))
def test_not_due_object_iteration_opened_today(self):
"""interval=1 day: object with iteration opened today is NOT due."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 15, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(app, list(rule._get_newly_due_objects()))
def test_due_independent_of_task_execution_time(self):
"""Due detection gives the same result whether the task runs at 00:00:01 or 23:59:59."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
opened_on = dt.datetime(2025, 6, 14, 18, 0, 0, tzinfo=dt.UTC)
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
with patch("django.utils.timezone.now", return_value=task_time):
self.assertIn(app, list(rule._get_newly_due_objects()))
def test_due_boundary_multi_day_interval(self):
"""interval=30 days: due after 30 full days, not after 29."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=30")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
# Previous review opened 30 days ago (May 16), review is due for the object
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 16, 12, 0, 0, tzinfo=dt.UTC)
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(app, list(rule._get_newly_due_objects()))
# Previous review opened 29 days ago (May 17), new review is NOT due
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 17, 12, 0, 0, tzinfo=dt.UTC)
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(app, list(rule._get_newly_due_objects()))
def test_apply_overdue_at_boundary(self):
"""apply() marks iteration overdue when grace period just expired,
regardless of what time the daily task runs."""
_, rule, iteration = self._create_rule_and_iteration(
grace_period="days=1", interval="days=365"
)
opened_on = dt.datetime(2025, 6, 14, 20, 0, 0, tzinfo=dt.UTC)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=task_time):
rule.apply()
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.OVERDUE)

View File

@@ -1,3 +1,4 @@
from datetime import datetime
from urllib import parse
from django.contrib.contenttypes.models import ContentType
@@ -39,6 +40,10 @@ def link_for_model(model: Model) -> str:
return f"{reverse("authentik_core:if-admin")}#{admin_link_for_model(model)}"
def start_of_day(dt: datetime) -> datetime:
return dt.replace(hour=0, minute=0, second=0, microsecond=0)
class ContentTypeField(ChoiceField):
def __init__(self, **kwargs):
super().__init__(choices=model_choices(), **kwargs)

View File

@@ -78,7 +78,8 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
def create(self, user: User):
"""Create user from scratch and create a connection object"""
microsoft_user = self.to_schema(user, None)
self.check_email_valid(microsoft_user.user_principal_name)
if microsoft_user.user_principal_name:
self.check_email_valid(microsoft_user.user_principal_name)
with transaction.atomic():
try:
response = self._request(self.client.users.post(microsoft_user))
@@ -118,7 +119,8 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
def update(self, user: User, connection: MicrosoftEntraProviderUser):
"""Update existing user"""
microsoft_user = self.to_schema(user, connection)
self.check_email_valid(microsoft_user.user_principal_name)
if microsoft_user.user_principal_name:
self.check_email_valid(microsoft_user.user_principal_name)
response = self._request(
self.client.users.by_user_id(connection.microsoft_id).patch(microsoft_user)
)

View File

@@ -185,6 +185,16 @@ class PolicyEngine:
# Only call .recv() if no result is saved, otherwise we just deadlock here
if not proc_info.result:
proc_info.result = proc_info.connection.recv()
if proc_info.result and proc_info.result._exec_time:
HIST_POLICIES_EXECUTION_TIME.labels(
binding_order=proc_info.binding.order,
binding_target_type=proc_info.binding.target_type,
binding_target_name=proc_info.binding.target_name,
object_type=(
class_to_path(self.request.obj.__class__) if self.request.obj else ""
),
mode="execute_process",
).observe(proc_info.result._exec_time)
return self
@property

View File

@@ -2,6 +2,7 @@
from multiprocessing import get_context
from multiprocessing.connection import Connection
from time import perf_counter
from django.core.cache import cache
from sentry_sdk import start_span
@@ -11,8 +12,6 @@ from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.reflection import class_to_path
from authentik.policies.apps import HIST_POLICIES_EXECUTION_TIME
from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding
from authentik.policies.types import CACHE_PREFIX, PolicyRequest, PolicyResult
@@ -123,18 +122,9 @@ class PolicyProcess(PROCESS_CLASS):
def profiling_wrapper(self):
"""Run with profiling enabled"""
with (
start_span(
op="authentik.policy.process.execute",
) as span,
HIST_POLICIES_EXECUTION_TIME.labels(
binding_order=self.binding.order,
binding_target_type=self.binding.target_type,
binding_target_name=self.binding.target_name,
object_type=class_to_path(self.request.obj.__class__) if self.request.obj else "",
mode="execute_process",
).time(),
):
with start_span(
op="authentik.policy.process.execute",
) as span:
span: Span
span.set_data("policy", self.binding.policy)
span.set_data("request", self.request)
@@ -142,8 +132,14 @@ class PolicyProcess(PROCESS_CLASS):
def run(self): # pragma: no cover
"""Task wrapper to run policy checking"""
result = None
try:
self.connection.send(self.profiling_wrapper())
start = perf_counter()
result = self.profiling_wrapper()
end = perf_counter()
result._exec_time = max((end - start), 0)
except Exception as exc: # noqa
LOGGER.warning("Policy failed to run", exc=exc)
self.connection.send(PolicyResult(False, str(exc)))
result = PolicyResult(False, str(exc))
finally:
self.connection.send(result)

View File

@@ -77,6 +77,8 @@ class PolicyResult:
log_messages: list[LogEvent] | None
_exec_time: int | None
def __init__(self, passing: bool, *messages: str):
self.passing = passing
self.messages = messages
@@ -84,6 +86,7 @@ class PolicyResult:
self.source_binding = None
self.source_results = []
self.log_messages = []
self._exec_time = None
def __repr__(self):
return self.__str__()

View File

@@ -1,5 +1,6 @@
"""Device backchannel tests"""
from base64 import b64encode
from json import loads
from django.urls import reverse
@@ -26,7 +27,7 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
provider=self.provider,
)
def test_backchannel_invalid(self):
def test_backchannel_invalid_client_id_via_post_body(self):
"""Test backchannel"""
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
@@ -50,7 +51,7 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
)
self.assertEqual(res.status_code, 400)
def test_backchannel(self):
def test_backchannel_client_id_via_post_body(self):
"""Test backchannel"""
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
@@ -61,3 +62,37 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)
def test_backchannel_invalid_client_id_via_auth_header(self):
"""Test backchannel"""
creds = b64encode(b"foo:").decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
)
self.assertEqual(res.status_code, 400)
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
)
self.assertEqual(res.status_code, 400)
# test without application
self.application.provider = None
self.application.save()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
data={
"client_id": "test",
},
)
self.assertEqual(res.status_code, 400)
def test_backchannel_client_id_via_auth_header(self):
"""Test backchannel"""
creds = b64encode(f"{self.provider.client_id}:".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
)
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)

View File

@@ -16,7 +16,7 @@ from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.errors import DeviceCodeError
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
from authentik.providers.oauth2.utils import TokenResponse
from authentik.providers.oauth2.utils import TokenResponse, extract_client_auth
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
LOGGER = get_logger()
@@ -32,7 +32,7 @@ class DeviceView(View):
def parse_request(self):
"""Parse incoming request"""
client_id = self.request.POST.get("client_id", None)
client_id, _ = extract_client_auth(self.request)
if not client_id:
raise DeviceCodeError("invalid_client")
provider = OAuth2Provider.objects.filter(client_id=client_id).first()

View File

@@ -14,24 +14,6 @@ class SAMLException(SentryIgnoredException):
return self.default_message
class MissingSAMLResponse(SAMLException):
"""Exception raised when request does not contain SAML Response."""
default_message = "Request does not contain a SAML response."
class UnsupportedNameIDFormat(SAMLException):
"""Exception raised when SAML Response contains NameID Format not supported."""
default_message = "The NameID Format in the SAML Response is not supported."
class MismatchedRequestID(SAMLException):
"""Exception raised when the returned request ID doesn't match the saved ID."""
default_message = "The SAML Response ID does not match the original request ID."
class InvalidEncryption(SAMLException):
"""Encryption of XML Object is either missing or invalid."""
@@ -42,3 +24,21 @@ class InvalidSignature(SAMLException):
"""Signature of XML Object is either missing or invalid."""
default_message = "The signature of the SAML object is either missing or invalid."
class MismatchedRequestID(SAMLException):
"""Exception raised when the returned request ID doesn't match the saved ID."""
default_message = "The SAML Response ID does not match the original request ID."
class MissingSAMLResponse(SAMLException):
"""Exception raised when request does not contain SAML Response."""
default_message = "Request does not contain a SAML response."
class UnsupportedNameIDFormat(SAMLException):
"""Exception raised when SAML Response contains NameID Format not supported."""
default_message = "The NameID Format in the SAML Response is not supported."

View File

@@ -4,6 +4,7 @@ from urllib.parse import parse_qsl, urlparse, urlunparse
from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import SuspiciousOperation
from django.http import Http404, HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect
@@ -37,7 +38,9 @@ from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSI
from authentik.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import nice64
from authentik.sources.saml.exceptions import (
InvalidEncryption,
InvalidSignature,
MismatchedRequestID,
MissingSAMLResponse,
UnsupportedNameIDFormat,
)
@@ -156,7 +159,15 @@ class ACSView(View):
processor = ResponseProcessor(source, request)
try:
processor.parse()
except (InvalidSignature, MissingSAMLResponse, VerificationError, ValueError) as exc:
except (
InvalidEncryption,
InvalidSignature,
MismatchedRequestID,
MissingSAMLResponse,
SuspiciousOperation,
VerificationError,
ValueError,
) as exc:
return bad_request_message(request, str(exc))
try:

View File

@@ -6,6 +6,7 @@ from django.contrib.auth.views import redirect_to_login
from django.http.request import HttpRequest
from structlog.stdlib import get_logger
from authentik.core.middleware import get_user
from authentik.core.models import Session
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
@@ -54,11 +55,13 @@ class SessionBindingBroken(SentryIgnoredException):
def logout_extra(request: HttpRequest, exc: SessionBindingBroken):
"""Similar to django's logout method, but able to carry more info to the signal"""
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user = getattr(request, "user", None)
# Since this middleware runs before the AuthenticationMiddleware, we can't use `request.user`
# as it hasn't been populated yet.
user = get_user(request)
if not getattr(user, "is_authenticated", True):
user = None
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user_logged_out.send(
sender=user.__class__, request=request, user=user, event_extra=exc.to_event()
)

View File

@@ -10,6 +10,8 @@ from django.utils.timezone import now
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import AuthenticatedSession, Session
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
@@ -270,6 +272,7 @@ class TestUserLoginStage(FlowTestCase):
def test_session_binding_broken(self):
"""Test session binding"""
Event.objects.all().delete()
self.client.force_login(self.user)
session = self.client.session
session[Session.Keys.LAST_IP] = "192.0.2.1"
@@ -285,3 +288,5 @@ class TestUserLoginStage(FlowTestCase):
)
+ f"?{NEXT_ARG_NAME}={reverse("authentik_api:user-me")}",
)
event = Event.objects.filter(action=EventAction.LOGOUT).first()
self.assertEqual(event.user, get_user(self.user))

View File

@@ -76,7 +76,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
}
}
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path)
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.EscapedPath())
if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")

View File

@@ -27,6 +27,24 @@ func TestRedirectToStart_Proxy(t *testing.T) {
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
}
func TestRedirectToStart_Proxy_EncodedSlash(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_PROXY.Ptr()
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
// %2F is a URL-encoded forward slash, used by apps like RabbitMQ in queue paths
req, _ := http.NewRequest("GET", "/api/queues/%2F/MYChannelCreated", nil)
rr := httptest.NewRecorder()
a.redirectToStart(rr, req)
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
assert.Contains(t, loc.String(), "%252F", "encoded slash %2F must be preserved in redirect URL")
s, _ := a.sessions.Get(req, a.SessionName())
assert.Contains(t, s.Values[constants.SessionRedirect].(string), "%2F", "encoded slash %2F must be preserved in session redirect")
}
func TestRedirectToStart_Forward(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_SINGLE.Ptr()

View File

@@ -80,7 +80,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.10.4@sha256:4cac394b6b72846f8a85a7a0e577c6d61d4e17fe2ccee65d9451a8b3c9efb4ac AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.14.3-slim-trixie-fips@sha256:c2726911d327b377501adb6c1ca9ba6b9bde8feb09bc8787cd358413784fe39f AS python-base
FROM ghcr.io/goauthentik/fips-python:3.14.3-slim-trixie-fips@sha256:bccefeecbdd7b5895053c97b7cc24327380934e959a34b1331a1489201ed3df3 AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \

View File

@@ -31,7 +31,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/ldap ./cmd/ldap
# Stage 2: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:b0917afc8d7d3dea6cf8e741fee59bbd40776b9a98e6fdac16143adac2c45394
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:d6def0a23db74f699199c7d72fe57e2313982a51eeedc4883039b22538f6ed02
ARG VERSION
ARG GIT_BUILD_HASH

View File

@@ -47,7 +47,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/proxy ./cmd/proxy
# Stage 3: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:b0917afc8d7d3dea6cf8e741fee59bbd40776b9a98e6fdac16143adac2c45394
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:d6def0a23db74f699199c7d72fe57e2313982a51eeedc4883039b22538f6ed02
ARG VERSION
ARG GIT_BUILD_HASH

View File

@@ -31,7 +31,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/radius ./cmd/radius
# Stage 2: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:b0917afc8d7d3dea6cf8e741fee59bbd40776b9a98e6fdac16143adac2c45394
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:d6def0a23db74f699199c7d72fe57e2313982a51eeedc4883039b22538f6ed02
ARG VERSION
ARG GIT_BUILD_HASH

666
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,12 @@
"name": "@goauthentik/authentik",
"version": "2026.5.0-rc1",
"private": true,
"scripts": {
"lint": "run-s lint:spellcheck lint:lockfile",
"lint:lockfile": "echo 'Skipping lockfile linting'",
"lint:node": "echo 'Skipping node linting'",
"lint:spellcheck": "echo 'Skipping spellcheck linting'"
},
"type": "module",
"dependencies": {
"@eslint/js": "^9.39.1",
@@ -12,31 +18,31 @@
"@typescript-eslint/parser": "^8.48.1",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.1"
},
"workspaces": [],
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
"name": "node",
"onFail": "ignore",
"version": "24"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "ignore"
}
},
"workspaces": [],
"prettier": "@goauthentik/prettier-config",
"overrides": {
"format-imports": {
"eslint": "$eslint"
}
},
"devEngines": {
"runtime": {
"name": "node",
"onFail": "warn",
"version": ">=24"
},
"packageManager": {
"name": "npm",
"onFail": "warn",
"version": ">=11.6.2"
}
}
}

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "2.3.0",
"version": "2.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/docusaurus-config",
"version": "2.3.0",
"version": "2.4.0",
"license": "MIT",
"dependencies": {
"deepmerge-ts": "^7.1.5",
@@ -27,14 +27,14 @@
"@typescript-eslint/parser": "^8.47.0",
"eslint": "^9.39.1",
"pino": "^10.1.0",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.0"
},
"optionalDependencies": {
"react": ">=18",
@@ -61,7 +61,7 @@
},
"../eslint-config": {
"name": "@goauthentik/eslint-config",
"version": "1.2.0",
"version": "1.3.0",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -101,7 +101,7 @@
},
"../prettier-config": {
"name": "@goauthentik/prettier-config",
"version": "3.3.1",
"version": "3.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -115,18 +115,18 @@
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.0"
},
"peerDependencies": {
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20"
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0"
}
},
"../tsconfig": {
@@ -4699,19 +4699,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@pnpm/config.env-replace": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
@@ -9426,9 +9413,9 @@
}
},
"node_modules/git-hooks-list": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.1.1.tgz",
"integrity": "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.2.1.tgz",
"integrity": "sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -16131,9 +16118,9 @@
}
},
"node_modules/prettier": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -16147,17 +16134,16 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.20",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.20.tgz",
"integrity": "sha512-G8cowPh+QmJJECTZlrPDKWkVVcwrFjF2rGcw546w3N8blLoc4szSs8UUPfFVxHUNLUjiru71Ah83g1lZkeK9Bw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-3.0.0.tgz",
"integrity": "sha512-z8/QmPSqx/ANvvQMWJSkSq1+ihBXeuwDEYdjX3ZjRJ5Ty1k7vGbFQfhzk2eDe0rwS/TNyRjWK/qnjJEStAOtDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "3.5.0",
"synckit": "0.11.11"
"sort-package-json": "3.6.0"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
"prettier": "^3"
},
"peerDependenciesMeta": {
"prettier": {
@@ -17886,26 +17872,26 @@
}
},
"node_modules/sort-object-keys": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.0.1.tgz",
"integrity": "sha512-R89fO+z3x7hiKPXX5P0qim+ge6Y60AjtlW+QQpRozrrNcR1lw9Pkpm5MLB56HoNvdcLHL4wbpq16OcvGpEDJIg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.1.0.tgz",
"integrity": "sha512-SOiEnthkJKPv2L6ec6HMwhUcN0/lppkeYuN1x63PbyPRrgSPIuBJCiYxYyvWRTtjMlOi14vQUCGUJqS6PLVm8g==",
"dev": true,
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.5.0.tgz",
"integrity": "sha512-moY4UtptUuP5sPuu9H9dp8xHNel7eP5/Kz/7+90jTvC0IOiPH2LigtRM/aSFSxreaWoToHUVUpEV4a2tAs2oKQ==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.6.0.tgz",
"integrity": "sha512-fyJsPLhWvY7u2KsKPZn1PixbXp+1m7V8NWqU8CvgFRbMEX41Ffw1kD8n0CfJiGoaSfoAvbrqRRl/DcHO8omQOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-indent": "^7.0.1",
"detect-indent": "^7.0.2",
"detect-newline": "^4.0.1",
"git-hooks-list": "^4.0.0",
"git-hooks-list": "^4.1.1",
"is-plain-obj": "^4.1.0",
"semver": "^7.7.1",
"sort-object-keys": "^2.0.0",
"tinyglobby": "^0.2.12"
"semver": "^7.7.3",
"sort-object-keys": "^2.0.1",
"tinyglobby": "^0.2.15"
},
"bin": {
"sort-package-json": "cli.js"
@@ -18341,22 +18327,6 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tapable": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",

View File

@@ -1,8 +1,13 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "2.3.0",
"version": "2.4.0",
"description": "authentik's Docusaurus config",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/docusaurus-config"
},
"scripts": {
"build": "tsc -p .",
"lint": "eslint --fix .",
@@ -11,11 +16,12 @@
"prettier-check": "prettier --cache --check -u ."
},
"type": "module",
"types": "./out/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
"types": "./out/index.d.ts",
"import": "./index.js"
},
"./css/*.css": "./css/*.css"
},
@@ -38,8 +44,8 @@
"@typescript-eslint/parser": "^8.47.0",
"eslint": "^9.39.1",
"pino": "^10.1.0",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
@@ -54,22 +60,28 @@
"react": ">=18",
"react-dom": ">=18"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/docusaurus-config"
},
"types": "./out/index.d.ts",
"files": [
"./index.js",
"lib/**/*",
"css/**/*",
"out/**/*"
],
"engines": {
"node": ">=24",
"npm": ">=11.10.0"
},
"devEngines": {
"runtime": {
"name": "node",
"onFail": "warn",
"version": ">=24"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "warn"
}
},
"prettier": "@goauthentik/prettier-config",
"peerDependenciesMeta": {
"@docusaurus/theme-search-algolia": {

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.4.0",
"version": "1.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.4.0",
"version": "1.5.0",
"license": "MIT",
"dependencies": {
"find-free-ports": "^3.1.1"
@@ -22,8 +22,8 @@
"esbuild": "^0.27.1",
"eslint": "^9.39.1",
"pino": "^10.1.0",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typedoc": "^0.28.15",
"typedoc-plugin-markdown": "^4.9.0",
"typescript": "^5.9.3",
@@ -39,7 +39,7 @@
},
"../eslint-config": {
"name": "@goauthentik/eslint-config",
"version": "1.2.0",
"version": "1.2.1",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -60,7 +60,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
@@ -79,7 +79,7 @@
},
"../prettier-config": {
"name": "@goauthentik/prettier-config",
"version": "3.3.1",
"version": "3.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -93,18 +93,18 @@
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20"
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0"
}
},
"../tsconfig": {
@@ -833,19 +833,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "3.19.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.19.0.tgz",
@@ -971,7 +958,6 @@
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0",
@@ -1176,7 +1162,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1455,7 +1440,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -1761,9 +1745,9 @@
"license": "ISC"
},
"node_modules/git-hooks-list": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.1.1.tgz",
"integrity": "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.2.1.tgz",
"integrity": "sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -2138,7 +2122,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -2197,12 +2180,11 @@
}
},
"node_modules/prettier": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -2214,17 +2196,16 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.20",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.20.tgz",
"integrity": "sha512-G8cowPh+QmJJECTZlrPDKWkVVcwrFjF2rGcw546w3N8blLoc4szSs8UUPfFVxHUNLUjiru71Ah83g1lZkeK9Bw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-3.0.0.tgz",
"integrity": "sha512-z8/QmPSqx/ANvvQMWJSkSq1+ihBXeuwDEYdjX3ZjRJ5Ty1k7vGbFQfhzk2eDe0rwS/TNyRjWK/qnjJEStAOtDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "3.5.0",
"synckit": "0.11.11"
"sort-package-json": "3.6.0"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
"prettier": "^3"
},
"peerDependenciesMeta": {
"prettier": {
@@ -2353,26 +2334,26 @@
}
},
"node_modules/sort-object-keys": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.0.1.tgz",
"integrity": "sha512-R89fO+z3x7hiKPXX5P0qim+ge6Y60AjtlW+QQpRozrrNcR1lw9Pkpm5MLB56HoNvdcLHL4wbpq16OcvGpEDJIg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.1.0.tgz",
"integrity": "sha512-SOiEnthkJKPv2L6ec6HMwhUcN0/lppkeYuN1x63PbyPRrgSPIuBJCiYxYyvWRTtjMlOi14vQUCGUJqS6PLVm8g==",
"dev": true,
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.5.0.tgz",
"integrity": "sha512-moY4UtptUuP5sPuu9H9dp8xHNel7eP5/Kz/7+90jTvC0IOiPH2LigtRM/aSFSxreaWoToHUVUpEV4a2tAs2oKQ==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.6.0.tgz",
"integrity": "sha512-fyJsPLhWvY7u2KsKPZn1PixbXp+1m7V8NWqU8CvgFRbMEX41Ffw1kD8n0CfJiGoaSfoAvbrqRRl/DcHO8omQOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-indent": "^7.0.1",
"detect-indent": "^7.0.2",
"detect-newline": "^4.0.1",
"git-hooks-list": "^4.0.0",
"git-hooks-list": "^4.1.1",
"is-plain-obj": "^4.1.0",
"semver": "^7.7.1",
"sort-object-keys": "^2.0.0",
"tinyglobby": "^0.2.12"
"semver": "^7.7.3",
"sort-object-keys": "^2.0.1",
"tinyglobby": "^0.2.15"
},
"bin": {
"sort-package-json": "cli.js"
@@ -2417,22 +2398,6 @@
"node": ">=8"
}
},
"node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/thread-stream": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
@@ -2492,7 +2457,6 @@
"integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@gerrit0/mini-shiki": "^3.17.0",
"lunr": "^2.3.9",
@@ -2530,7 +2494,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -1,8 +1,13 @@
{
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.4.0",
"version": "1.5.0",
"description": "ESBuild + browser refresh. Build completes, page reloads.",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/esbuild-plugin-live-reload"
},
"scripts": {
"build": "npm run build:types && npm run build:docs",
"build:docs": "typedoc",
@@ -14,6 +19,7 @@
},
"main": "index.js",
"type": "module",
"types": "./out/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
@@ -47,8 +53,8 @@
"esbuild": "^0.27.1",
"eslint": "^9.39.1",
"pino": "^10.1.0",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typedoc": "^0.28.15",
"typedoc-plugin-markdown": "^4.9.0",
"typescript": "^5.9.3",
@@ -57,10 +63,30 @@
"peerDependencies": {
"esbuild": "^0.27.0"
},
"files": [
"./index.js",
"client/**/*",
"plugin/**/*",
"shared/**/*",
"out/**/*"
],
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
},
"devEngines": {
"runtime": {
"name": "node",
"onFail": "warn",
"version": ">=24"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "warn"
}
},
"prettier": "@goauthentik/prettier-config",
"keywords": [
"esbuild",
"live-reload",
@@ -69,20 +95,6 @@
"reload",
"authentik"
],
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/esbuild-plugin-live-reload"
},
"types": "./out/index.d.ts",
"files": [
"./index.js",
"client/**/*",
"plugin/**/*",
"shared/**/*",
"out/**/*"
],
"prettier": "@goauthentik/prettier-config",
"publishConfig": {
"access": "public"
}

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/eslint-config",
"version": "1.2.0",
"version": "1.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/eslint-config",
"version": "1.2.0",
"version": "1.2.1",
"license": "MIT",
"dependencies": {
"eslint": "^9.39.1",
@@ -26,7 +26,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
@@ -45,7 +45,7 @@
},
"../prettier-config": {
"name": "@goauthentik/prettier-config",
"version": "3.3.1",
"version": "3.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -59,18 +59,18 @@
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20"
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0"
}
},
"../tsconfig": {
@@ -111,7 +111,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@@ -658,7 +657,6 @@
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0",
@@ -888,7 +886,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -906,9 +903,9 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@@ -1162,7 +1159,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
@@ -1648,7 +1644,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3212,7 +3207,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3820,7 +3814,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4049,7 +4042,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View File

@@ -1,8 +1,13 @@
{
"name": "@goauthentik/eslint-config",
"version": "1.2.0",
"version": "1.2.1",
"description": "authentik's ESLint config",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/eslint-config"
},
"scripts": {
"build": "tsc -p .",
"lint": "eslint --fix .",
@@ -11,6 +16,7 @@
"prettier-check": "prettier --cache --check -u ."
},
"type": "module",
"types": "./out/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
@@ -52,21 +58,27 @@
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/eslint-config"
},
"types": "./out/index.d.ts",
"files": [
"./index.js",
"lib/**/*",
"out/**/*"
],
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
"name": "node",
"version": "24",
"onFail": "ignore"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "ignore"
}
},
"prettier": "@goauthentik/prettier-config",
"peerDependenciesMeta": {
"react": {

View File

@@ -11,10 +11,86 @@ import { fileURLToPath } from "node:url";
* @property {string[]} [packageSortOrder] Custom ordering array.
*/
/**
* @typedef {PrettierConfig & PackageJSONPluginConfig} ExtendedPrettierConfig
*/
const CI = !!process.env.CI;
/**
* @type {ExtendedPrettierConfig['plugins']}
*/
const plugins = [
// ---
fileURLToPath(import.meta.resolve("@goauthentik/prettier-config/imports-plugin")),
];
/**
* @type {ExtendedPrettierConfig['overrides']}
*/
const overrides = [
{
files: "schemas/**/*.json",
options: {
tabWidth: 2,
},
},
{
files: "tsconfig.json",
options: {
trailingComma: "none",
},
},
];
// Sort order can be a source of false-positives in CI when this package is updated.
if (!CI) {
plugins.unshift("prettier-plugin-packagejson");
overrides.push({
files: "package.json",
options: {
packageSortOrder: [
// ---
"name",
"version",
"description",
"license",
"private",
"author",
"authors",
"contributors",
"funding",
"repository",
"bugs",
"homepage",
"scripts",
"main",
"type",
"types",
"exports",
"imports",
"dependencies",
"devDependencies",
"peerDependencies",
"optionalDependencies",
"workspaces",
"files",
"wireit",
"resolutions",
"engines",
"devEngines",
"packageManager",
"prettier",
"eslintConfig",
],
},
});
}
/**
* authentik Prettier configuration.
*
* @type {PrettierConfig & PackageJSONPluginConfig}
* @type {ExtendedPrettierConfig}
* @internal
*/
export const AuthentikPrettierConfig = {
@@ -34,51 +110,6 @@ export const AuthentikPrettierConfig = {
trailingComma: "all",
useTabs: false,
vueIndentScriptAndStyle: false,
plugins: [
// ---
"prettier-plugin-packagejson",
fileURLToPath(import.meta.resolve("@goauthentik/prettier-config/imports-plugin")),
],
overrides: [
{
files: "schemas/**/*.json",
options: {
tabWidth: 2,
},
},
{
files: "tsconfig.json",
options: {
trailingComma: "none",
},
},
{
files: "package.json",
options: {
packageSortOrder: [
// ---
"name",
"version",
"description",
"license",
"private",
"author",
"authors",
"scripts",
"main",
"type",
"exports",
"imports",
"dependencies",
"devDependencies",
"peerDependencies",
"optionalDependencies",
"wireit",
"resolutions",
"engines",
],
},
},
],
plugins,
overrides,
};

View File

@@ -1,7 +1,7 @@
import { format } from "prettier";
import { AuthentikPrettierConfig } from "./constants.js";
import { format } from "prettier";
/**
* Format using Prettier.
*

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/prettier-config",
"version": "3.3.1",
"version": "3.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/prettier-config",
"version": "3.3.1",
"version": "3.4.0",
"license": "MIT",
"dependencies": {
"format-imports": "^4.0.8"
@@ -19,23 +19,23 @@
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20"
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0"
}
},
"../eslint-config": {
"name": "@goauthentik/eslint-config",
"version": "1.2.0",
"version": "1.2.1",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -56,7 +56,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
@@ -433,19 +433,6 @@
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -792,9 +779,9 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@@ -1353,9 +1340,9 @@
}
},
"node_modules/git-hooks-list": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.1.1.tgz",
"integrity": "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.2.1.tgz",
"integrity": "sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1799,9 +1786,9 @@
}
},
"node_modules/prettier": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
@@ -1814,17 +1801,16 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.20",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.20.tgz",
"integrity": "sha512-G8cowPh+QmJJECTZlrPDKWkVVcwrFjF2rGcw546w3N8blLoc4szSs8UUPfFVxHUNLUjiru71Ah83g1lZkeK9Bw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-3.0.0.tgz",
"integrity": "sha512-z8/QmPSqx/ANvvQMWJSkSq1+ihBXeuwDEYdjX3ZjRJ5Ty1k7vGbFQfhzk2eDe0rwS/TNyRjWK/qnjJEStAOtDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "3.5.0",
"synckit": "0.11.11"
"sort-package-json": "3.6.0"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
"prettier": "^3"
},
"peerDependenciesMeta": {
"prettier": {
@@ -1897,26 +1883,26 @@
}
},
"node_modules/sort-object-keys": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.0.1.tgz",
"integrity": "sha512-R89fO+z3x7hiKPXX5P0qim+ge6Y60AjtlW+QQpRozrrNcR1lw9Pkpm5MLB56HoNvdcLHL4wbpq16OcvGpEDJIg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.1.0.tgz",
"integrity": "sha512-SOiEnthkJKPv2L6ec6HMwhUcN0/lppkeYuN1x63PbyPRrgSPIuBJCiYxYyvWRTtjMlOi14vQUCGUJqS6PLVm8g==",
"dev": true,
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.5.0.tgz",
"integrity": "sha512-moY4UtptUuP5sPuu9H9dp8xHNel7eP5/Kz/7+90jTvC0IOiPH2LigtRM/aSFSxreaWoToHUVUpEV4a2tAs2oKQ==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.6.0.tgz",
"integrity": "sha512-fyJsPLhWvY7u2KsKPZn1PixbXp+1m7V8NWqU8CvgFRbMEX41Ffw1kD8n0CfJiGoaSfoAvbrqRRl/DcHO8omQOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-indent": "^7.0.1",
"detect-indent": "^7.0.2",
"detect-newline": "^4.0.1",
"git-hooks-list": "^4.0.0",
"git-hooks-list": "^4.1.1",
"is-plain-obj": "^4.1.0",
"semver": "^7.7.1",
"sort-object-keys": "^2.0.0",
"tinyglobby": "^0.2.12"
"semver": "^7.7.3",
"sort-object-keys": "^2.0.1",
"tinyglobby": "^0.2.15"
},
"bin": {
"sort-package-json": "cli.js"
@@ -2004,22 +1990,6 @@
"node": ">=8"
}
},
"node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",

View File

@@ -1,8 +1,13 @@
{
"name": "@goauthentik/prettier-config",
"version": "3.3.1",
"version": "3.4.0",
"description": "authentik's Prettier config",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/prettier-config"
},
"scripts": {
"build": "tsc -p .",
"lint": "eslint --fix .",
@@ -11,19 +16,20 @@
"prettier-check": "prettier --check ."
},
"type": "module",
"types": "./out/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
"types": "./out/index.d.ts",
"import": "./index.js"
},
"./imports-plugin": {
"import": "./lib/imports.js",
"types": "./out/lib/imports.d.ts"
"types": "./out/lib/imports.d.ts",
"import": "./lib/imports.js"
},
"./formatter": {
"import": "./lib/formatter.js",
"types": "./out/lib/formatter.d.ts"
"types": "./out/lib/formatter.d.ts",
"import": "./lib/formatter.js"
}
},
"dependencies": {
@@ -37,30 +43,36 @@
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0"
},
"peerDependencies": {
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.20"
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/prettier-config"
},
"types": "./out/index.d.ts",
"files": [
"./index.js",
"lib/**/*",
"out/**/*"
],
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
"name": "node",
"version": "24",
"onFail": "ignore"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "ignore"
}
},
"prettier": "./index.js",
"overrides": {
"format-imports": {

View File

@@ -44,7 +44,7 @@ dependencies = [
"kubernetes==35.0.0",
"ldap3==2.9.1",
"lxml==6.0.2",
"msgraph-sdk==1.54.0",
"msgraph-sdk==1.55.0",
"opencontainers==0.0.15",
"packaging==26.0",
"paramiko==4.0.0",
@@ -76,13 +76,13 @@ dependencies = [
[dependency-groups]
dev = [
"aws-cdk-lib==2.238.0",
"aws-cdk-lib==2.239.0",
"bandit==1.9.3",
"black==26.1.0",
"bpython==0.26",
"codespell==2.4.1",
"colorama==0.4.6",
"constructs==10.5.0",
"constructs==10.5.1",
"coverage[toml]==7.13.4",
"daphne==4.2.1",
"debugpy==1.8.20",
@@ -102,8 +102,8 @@ dev = [
"pytest-timeout==2.4.0",
"pytest==9.0.2",
"requests-mock==1.12.1",
"ruff==0.15.1",
"selenium==4.40.0",
"ruff==0.15.2",
"selenium==4.41.0",
"types-channels==4.3.0.20250822",
"types-docker==7.1.0.20260109",
"types-jwcrypto==1.5.0.20251102",

View File

@@ -42199,15 +42199,15 @@ components:
readOnly: true
opened_on:
type: string
format: date
format: date-time
readOnly: true
grace_period_end:
type: string
format: date
format: date-time
readOnly: true
next_review_date:
type: string
format: date
format: date-time
readOnly: true
reviews:
type: array

View File

@@ -110,8 +110,8 @@ class TestFlowsEnroll(SeleniumTestCase):
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
wait = WebDriverWait(identification_stage, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[name='enroll']")))
identification_stage.find_element(By.CSS_SELECTOR, "a[name='enroll']").click()
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[ouiaId='enroll']")))
identification_stage.find_element(By.CSS_SELECTOR, "a[ouiaId='enroll']").click()
# First prompt stage
flow_executor = self.get_shadow_root("ak-flow-executor")

View File

@@ -26,8 +26,8 @@ class TestFlowsRecovery(SeleniumTestCase):
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
wait = WebDriverWait(identification_stage, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[name='recovery']")))
identification_stage.find_element(By.CSS_SELECTOR, "a[name='recovery']").click()
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[ouiaId='recovery']")))
identification_stage.find_element(By.CSS_SELECTOR, "a[ouiaId='recovery']").click()
# First prompt stage
flow_executor = self.get_shadow_root("ak-flow-executor")

125
uv.lock generated
View File

@@ -192,15 +192,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" },
]
[[package]]
name = "async-generator"
version = "1.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/b6/6fa6b3b598a03cba5e80f829e0dadbb49d7645f523d209b2fb7ea0bbb02a/async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144", size = 29870, upload-time = "2018-08-01T03:36:21.69Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/71/52/39d20e03abd0ac9159c162ec24b93fbcaa111e8400308f2465432495ca2b/async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b", size = 18857, upload-time = "2018-08-01T03:36:20.029Z" },
]
[[package]]
name = "attrs"
version = "25.4.0"
@@ -363,7 +354,7 @@ requires-dist = [
{ name = "kubernetes", specifier = "==35.0.0" },
{ name = "ldap3", specifier = "==2.9.1" },
{ name = "lxml", specifier = "==6.0.2" },
{ name = "msgraph-sdk", specifier = "==1.54.0" },
{ name = "msgraph-sdk", specifier = "==1.55.0" },
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
{ name = "packaging", specifier = "==26.0" },
{ name = "paramiko", specifier = "==4.0.0" },
@@ -395,13 +386,13 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
{ name = "aws-cdk-lib", specifier = "==2.238.0" },
{ name = "aws-cdk-lib", specifier = "==2.239.0" },
{ name = "bandit", specifier = "==1.9.3" },
{ name = "black", specifier = "==26.1.0" },
{ name = "bpython", specifier = "==0.26" },
{ name = "codespell", specifier = "==2.4.1" },
{ name = "colorama", specifier = "==0.4.6" },
{ name = "constructs", specifier = "==10.5.0" },
{ name = "constructs", specifier = "==10.5.1" },
{ name = "coverage", extras = ["toml"], specifier = "==7.13.4" },
{ name = "daphne", specifier = "==4.2.1" },
{ name = "debugpy", specifier = "==1.8.20" },
@@ -421,8 +412,8 @@ dev = [
{ name = "pytest-randomly", specifier = "==4.0.1" },
{ name = "pytest-timeout", specifier = "==2.4.0" },
{ name = "requests-mock", specifier = "==1.12.1" },
{ name = "ruff", specifier = "==0.15.1" },
{ name = "selenium", specifier = "==4.40.0" },
{ name = "ruff", specifier = "==0.15.2" },
{ name = "selenium", specifier = "==4.41.0" },
{ name = "types-channels", specifier = "==4.3.0.20250822" },
{ name = "types-docker", specifier = "==7.1.0.20260109" },
{ name = "types-jwcrypto", specifier = "==1.5.0.20251102" },
@@ -492,21 +483,21 @@ wheels = [
[[package]]
name = "aws-cdk-cloud-assembly-schema"
version = "48.20.0"
version = "50.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsii" },
{ name = "publication" },
{ name = "typeguard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/b5/1ce2f6bff913ca8c94a001b84290ec4ce3729f54a3af0e3ff0edb303ac20/aws_cdk_cloud_assembly_schema-48.20.0.tar.gz", hash = "sha256:229aa136c26b71b0a82b5a32658eabcd30e344f7e136315fdb6e3de8ef523bfa", size = 208109, upload-time = "2025-11-19T12:19:48.206Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/4c/a9ac7498f2b76d7697e60367b9a8c690fc54bb3bf4e591e6fe06977c847b/aws_cdk_cloud_assembly_schema-50.4.0.tar.gz", hash = "sha256:c9aa7a108ca63f3880f26594166d3e8c16b504a50424011baf785231dc009f30", size = 208573, upload-time = "2026-02-05T16:37:54.61Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/08/17a35f0b668451484f2254f5e50a0105958bffe90da11c41b7629972e6a9/aws_cdk_cloud_assembly_schema-48.20.0-py3-none-any.whl", hash = "sha256:f5b6cf661cac8690add9461de13aeae3f3742eec71c066032bd045b08d0b7c3e", size = 207669, upload-time = "2025-11-19T12:19:46.614Z" },
{ url = "https://files.pythonhosted.org/packages/50/37/946c9646606cf9dffa5d15a860557c9d507655c97fb2525bb8bd0c215179/aws_cdk_cloud_assembly_schema-50.4.0-py3-none-any.whl", hash = "sha256:3f98f06d99f68f5bae5c72f0f392494dd3ef4211197afd0e75cfe1d5fc487d1c", size = 208231, upload-time = "2026-02-05T16:37:52.037Z" },
]
[[package]]
name = "aws-cdk-lib"
version = "2.238.0"
version = "2.239.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aws-cdk-asset-awscli-v1" },
@@ -517,9 +508,9 @@ dependencies = [
{ name = "publication" },
{ name = "typeguard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3f/37/d9cdaf7068b5e598b2ab60687877de484adefc3927a7cf6571bff40da884/aws_cdk_lib-2.238.0.tar.gz", hash = "sha256:cef10c71e1575196df277fdac57c54010a8d28d77646da09200b1d1cb3625f8e", size = 47453139, upload-time = "2026-02-09T16:56:46.117Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e3/f8/851c18653bf6806877f5c942df7149c1d4f118063106b3c99c083d124da9/aws_cdk_lib-2.239.0.tar.gz", hash = "sha256:b5637f961e05b0d9ce28da2d759d605e23f4679f2cd0d1262efe3c32986d81f3", size = 47594912, upload-time = "2026-02-19T21:58:24.267Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/b9/47cb90169841bbbe74e2e30967132d8930a70e05340454caaa3e31b129ab/aws_cdk_lib-2.238.0-py3-none-any.whl", hash = "sha256:6602d6678597c649d80ff884972c3fa67a98fd6cacb56adcb77cd8fc4a735d43", size = 48104897, upload-time = "2026-02-09T16:56:06.79Z" },
{ url = "https://files.pythonhosted.org/packages/b8/05/d5293605890d315b2e91d3538b6ebdd7fa7704e9686a0beac76773ae6954/aws_cdk_lib-2.239.0-py3-none-any.whl", hash = "sha256:ef00581bb309440de8e4fbf0adc2ab53fa443a10783111a349c69bb25128ddad", size = 48242835, upload-time = "2026-02-19T21:57:38.138Z" },
]
[[package]]
@@ -856,16 +847,16 @@ wheels = [
[[package]]
name = "constructs"
version = "10.5.0"
version = "10.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsii" },
{ name = "publication" },
{ name = "typeguard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/35/e3/29744d401eb6597701cba93dbedee08450a6e8412718091b986e48502e49/constructs-10.5.0.tar.gz", hash = "sha256:88d23b10361c0a8b4896f6bed66693a0f4c61f0aa4247eb33a34a4ed12e67aaf", size = 68010, upload-time = "2026-02-17T09:48:08.71Z" }
sdist = { url = "https://files.pythonhosted.org/packages/df/a7/1d87d7327aaa8662f7525b064b6c0524e4b3985092840e6a568f5eb4a7d6/constructs-10.5.1.tar.gz", hash = "sha256:c0e90bb2b9c2782f292017820b91714321cb78393c8965c9362b0b624bfaf23b", size = 68165, upload-time = "2026-02-19T09:56:32.925Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4c/0a/8141f766828dc6f249bf08d4e3bb4a65018ff7a8c5caf3c5f3f46f90d8e7/constructs-10.5.0-py3-none-any.whl", hash = "sha256:01e613161f087f0432c24824a1ba4e8a3084f5af4c7fcd852c8e94bfc0110ab5", size = 66172, upload-time = "2026-02-17T09:48:07.218Z" },
{ url = "https://files.pythonhosted.org/packages/c3/20/dff420181946bcaba48dba3f0c11db202c8441eda927f281ad78912cf777/constructs-10.5.1-py3-none-any.whl", hash = "sha256:fc5c14f6b2770c8542a43e298aa29b63dee4b18701763e8c0fdce202624c3a7c", size = 66344, upload-time = "2026-02-19T09:56:31.311Z" },
]
[[package]]
@@ -2409,7 +2400,7 @@ wheels = [
[[package]]
name = "msgraph-sdk"
version = "1.54.0"
version = "1.55.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-identity" },
@@ -2419,9 +2410,9 @@ dependencies = [
{ name = "microsoft-kiota-serialization-text" },
{ name = "msgraph-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/56/0c2fba87b6eee19e4589bf21c3141de290afc51d654445fc3e2908244d7b/msgraph_sdk-1.54.0.tar.gz", hash = "sha256:4dc294fc7f8a173f5bee30ccfc396b81fa1a16d7dcc95debe76fdf706920e5b3", size = 6283861, upload-time = "2026-02-06T01:29:26.975Z" }
sdist = { url = "https://files.pythonhosted.org/packages/10/44/0b5a188addf6341b3da10dd207e444417de255f7c1651902ba72016a2843/msgraph_sdk-1.55.0.tar.gz", hash = "sha256:6df691a31954a050d26b8a678968017e157d940fb377f2a8a4e17a9741b98756", size = 6295669, upload-time = "2026-02-20T00:32:29.378Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/7a/d9eed36c7f309eb6c81c5b72b5b8bad40203487414ad2b9e371febc79885/msgraph_sdk-1.54.0-py3-none-any.whl", hash = "sha256:2b9894fd9f21ed9a71188e3d68bd1a9a58b2d1077e96ee4cb10a9f3d9d59a58e", size = 25707515, upload-time = "2026-02-06T01:29:23.344Z" },
{ url = "https://files.pythonhosted.org/packages/fb/a8/de807e62f8ff93003b573aa243cdcee2da2c0618b42efbc9a8e61aa7300d/msgraph_sdk-1.55.0-py3-none-any.whl", hash = "sha256:c8e68ebc4b88af5111de312e7fa910a4e76ddf48a4534feadb1fb8a411c48cfc", size = 25758742, upload-time = "2026-02-20T00:30:40.039Z" },
]
[[package]]
@@ -3285,27 +3276,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.15.1"
version = "0.15.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" }
sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" },
{ url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" },
{ url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" },
{ url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" },
{ url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" },
{ url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" },
{ url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" },
{ url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" },
{ url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" },
{ url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" },
{ url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" },
{ url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" },
{ url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" },
{ url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" },
{ url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" },
{ url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" },
{ url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" },
{ url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" },
{ url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" },
{ url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" },
{ url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" },
{ url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" },
{ url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" },
{ url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" },
{ url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" },
{ url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" },
{ url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" },
{ url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" },
{ url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" },
{ url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" },
{ url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" },
{ url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" },
{ url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" },
]
[[package]]
@@ -3334,22 +3325,19 @@ wheels = [
[[package]]
name = "selenium"
version = "4.40.0"
version = "4.41.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "trio" },
{ name = "trio-typing" },
{ name = "trio-websocket" },
{ name = "types-certifi" },
{ name = "types-urllib3" },
{ name = "typing-extensions" },
{ name = "urllib3", extra = ["socks"] },
{ name = "websocket-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/ef/a5727fa7b33d20d296322adf851b76072d8d3513e1b151969d3228437faf/selenium-4.40.0.tar.gz", hash = "sha256:a88f5905d88ad0b84991c2386ea39e2bbde6d6c334be38df5842318ba98eaa8c", size = 930444, upload-time = "2026-01-18T23:12:31.565Z" }
sdist = { url = "https://files.pythonhosted.org/packages/04/7c/133d00d6d013a17d3f39199f27f1a780ec2e95d7b9aa997dc1b8ac2e62a7/selenium-4.41.0.tar.gz", hash = "sha256:003e971f805231ad63e671783a2b91a299355d10cefb9de964c36ff3819115aa", size = 937872, upload-time = "2026-02-20T03:42:06.216Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/74/eb9d6540aca1911106fa0877b8e9ef24171bc18857937a6b0ffe0586c623/selenium-4.40.0-py3-none-any.whl", hash = "sha256:c8823fc02e2c771d9ad9a0cf899cee7de1a57a6697e3d0b91f67566129f2b729", size = 9608184, upload-time = "2026-01-18T23:12:29.435Z" },
{ url = "https://files.pythonhosted.org/packages/a8/d6/e4160989ef6b272779af6f3e5c43c3ba9be6687bdc21c68c3fb220e555b3/selenium-4.41.0-py3-none-any.whl", hash = "sha256:b8ccde8d2e7642221ca64af184a92c19eee6accf2e27f20f30472f5efae18eb1", size = 9532858, upload-time = "2026-02-20T03:42:03.218Z" },
]
[[package]]
@@ -3530,23 +3518,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/41/bf/945d527ff706233636c73880b22c7c953f3faeb9d6c7e2e85bfbfd0134a0/trio-0.32.0-py3-none-any.whl", hash = "sha256:4ab65984ef8370b79a76659ec87aa3a30c5c7c83ff250b4de88c29a8ab6123c5", size = 512030, upload-time = "2025-10-31T07:18:15.885Z" },
]
[[package]]
name = "trio-typing"
version = "0.10.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "async-generator" },
{ name = "importlib-metadata" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "trio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b5/74/a87aafa40ec3a37089148b859892cbe2eef08d132c816d58a60459be5337/trio-typing-0.10.0.tar.gz", hash = "sha256:065ee684296d52a8ab0e2374666301aec36ee5747ac0e7a61f230250f8907ac3", size = 38747, upload-time = "2023-12-01T02:54:55.508Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/ff/9bd795273eb14fac7f6a59d16cc8c4d0948a619a1193d375437c7f50f3eb/trio_typing-0.10.0-py3-none-any.whl", hash = "sha256:6d0e7ec9d837a2fe03591031a172533fbf4a1a95baf369edebfc51d5a49f0264", size = 42224, upload-time = "2023-12-01T02:54:54.1Z" },
]
[[package]]
name = "trio-websocket"
version = "0.12.2"
@@ -3619,15 +3590,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605, upload-time = "2021-12-10T21:09:37.844Z" },
]
[[package]]
name = "types-certifi"
version = "2021.10.8.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/52/68/943c3aeaf14624712a0357c4a67814dba5cea36d194f5c764dad7959a00c/types-certifi-2021.10.8.3.tar.gz", hash = "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f", size = 2095, upload-time = "2022-06-09T15:19:05.244Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/63/2463d89481e811f007b0e1cd0a91e52e141b47f9de724d20db7b861dcfec/types_certifi-2021.10.8.3-py3-none-any.whl", hash = "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a", size = 2136, upload-time = "2022-06-09T15:19:03.127Z" },
]
[[package]]
name = "types-channels"
version = "4.3.0.20250822"
@@ -3721,15 +3683,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" },
]
[[package]]
name = "types-urllib3"
version = "1.26.25.14"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/73/de/b9d7a68ad39092368fb21dd6194b362b98a1daeea5dcfef5e1adb5031c7e/types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f", size = 11239, upload-time = "2023-07-20T15:19:31.307Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/7b/3fc711b2efea5e85a7a0bbfe269ea944aa767bbba5ec52f9ee45d362ccf3/types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e", size = 15377, upload-time = "2023-07-20T15:19:30.379Z" },
]
[[package]]
name = "types-zxcvbn"
version = "4.5.0.20250809"

1832
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,17 +5,17 @@
"private": true,
"scripts": {
"build": "wireit",
"build:sfe": "npm run build -w @goauthentik/web-sfe",
"build-locales": "node scripts/build-locales.mjs",
"build-proxy": "wireit",
"build:sfe": "npm run build -w @goauthentik/web-sfe",
"bundler:watch": "node scripts/build-web.mjs --watch",
"extract-locales": "lit-localize extract",
"format": "wireit",
"lint": "eslint --fix .",
"lint-check": "eslint --max-warnings 0 .",
"lint:imports": "knip --config scripts/knip.config.ts",
"lint:lockfile": "wireit",
"lint:types": "wireit",
"lint-check": "eslint --max-warnings 0 .",
"lit-analyse": "wireit",
"precommit": "wireit",
"prettier": "prettier --cache --write -u .",
@@ -101,8 +101,8 @@
"@goauthentik/api": "^2026.2.0-rc1-1770744803",
"@goauthentik/core": "^1.0.0",
"@goauthentik/esbuild-plugin-live-reload": "^1.4.0",
"@goauthentik/eslint-config": "^1.2.0",
"@goauthentik/prettier-config": "^3.3.1",
"@goauthentik/eslint-config": "^1.2.1",
"@goauthentik/prettier-config": "^3.4.0",
"@goauthentik/tsconfig": "^1.0.5",
"@hcaptcha/types": "^1.1.0",
"@lit/context": "^1.1.6",
@@ -152,7 +152,7 @@
"globals": "^17.3.0",
"guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1",
"knip": "^5.84.1",
"knip": "^5.85.0",
"lex": "^2025.11.0",
"lit": "^3.3.2",
"lit-analyzer": "^2.0.3",
@@ -166,6 +166,7 @@
"pino-pretty": "^13.1.2",
"playwright": "^1.58.2",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"pseudolocale": "^2.2.0",
"rapidoc": "^9.3.8",
"react": "^19.2.4",
@@ -202,6 +203,9 @@
"@rollup/rollup-linux-x64-gnu": "^4.57.1",
"chromedriver": "^145.0.3"
},
"workspaces": [
"./packages/*"
],
"wireit": {
"build": {
"#comment": [
@@ -299,14 +303,27 @@
"node": ">=24",
"npm": ">=11.6.2"
},
"workspaces": [
"./packages/*"
],
"devEngines": {
"runtime": {
"name": "node",
"onFail": "warn",
"version": ">=24"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "warn"
}
},
"prettier": "@goauthentik/prettier-config",
"overrides": {
"@goauthentik/esbuild-plugin-live-reload": {
"esbuild": "$esbuild"
},
"@goauthentik/prettier-config": {
"prettier": "$prettier",
"prettier-plugin-packagejson": "$prettier-plugin-packagejson"
},
"@mrmarble/djangoql-completion": {
"lex": "$lex"
},

View File

@@ -9,6 +9,7 @@
},
"main": "index.js",
"type": "module",
"types": "./out/index.d.ts",
"exports": {
"./package.json": "./package.json",
"./*/browser": {
@@ -52,6 +53,5 @@
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
},
"types": "./out/index.d.ts"
}
}

View File

@@ -8,7 +8,7 @@ import "#admin/admin-overview/cards/WorkerStatusCard";
import "#admin/admin-overview/charts/AdminLoginAuthorizeChart";
import "#admin/admin-overview/charts/OutpostStatusChart";
import "#admin/admin-overview/charts/SyncStatusChart";
import "#elements/cards/AggregatePromiseCard";
import "#elements/cards/AggregateCard";
import "#elements/cards/QuickActionsCard";
import { formatUserDisplayName } from "#common/users";

View File

@@ -1,5 +1,5 @@
import "#admin/admin-overview/charts/AdminModelPerDay";
import "#elements/cards/AggregatePromiseCard";
import "#elements/cards/AggregateCard";
import { AKElement } from "#elements/Base";

View File

@@ -89,7 +89,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
html` <ak-forms-modal>
${StrictUnsafe<CustomFormElementTagName>(item.stageObj?.component, {
slot: "form",
instancePk: item.pk,
instancePk: item.stageObj?.pk,
actionLabel: msg("Update"),
headline: msg(str`Update ${item.stageObj?.verboseName}`, {
id: "form.headline.update",

View File

@@ -272,7 +272,7 @@ export class FlowViewPage extends AKElement {
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-bound-stages-list .target=${this.flow.pk}> </ak-bound-stages-list>
<ak-bound-stages-list target=${this.flow.pk}> </ak-bound-stages-list>
</div>
</div>
</div>

View File

@@ -11,6 +11,15 @@ export type DeepPartial<T> = T extends object
}
: T;
/**
* Type utility to make all properties in T recursively required.
*/
export type DeepRequired<T> = T extends object
? {
[P in keyof T]-?: DeepRequired<T[P]>;
}
: T;
/**
* Type utility to make readonly properties mutable.
*/

View File

@@ -49,7 +49,7 @@ export class AkDualSelectAvailablePane extends CustomEmitterElement<DualSelectEv
/* The array of key/value pairs this pane is currently showing */
@property({ type: Array })
public readonly options?: DualSelectPair[];
public options?: DualSelectPair[];
/**
* A set (set being easy for lookups) of keys with all the pairs selected,
@@ -57,7 +57,7 @@ export class AkDualSelectAvailablePane extends CustomEmitterElement<DualSelectEv
* can be marked and their clicks ignored.
*/
@property({ type: Object })
public readonly selected: Set<string | number> = new Set();
public selected: Set<string | number> = new Set();
//#endregion

View File

@@ -42,7 +42,7 @@ export class AkDualSelectSelectedPane extends CustomEmitterElement<DualSelectEve
/* The array of key/value pairs that are in the selected list. ALL of them. */
@property({ type: Array })
readonly selected: DualSelectPair[] = [];
public selected: DualSelectPair[] = [];
//#endregion

View File

@@ -1,120 +0,0 @@
import "../AggregatePromiseCard.js";
import { AggregatePromiseCard, type IAggregatePromiseCard } from "../AggregatePromiseCard.js";
import { ifPresent } from "#elements/utils/attributes";
import type { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
const metadata: Meta<AggregatePromiseCard> = {
title: "Elements/<ak-aggregate-card-promise>",
component: "ak-aggregate-card-promise",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: /* md */ `
# Aggregate Promise Cards
Aggregate Promise Cards are Aggregate Cards that take a promise from client code and either display
the contents of that promise or a pre-configured failure notice. The contents must be compliant with
and produce a meaningful result via the \`.toString()\` API. HTML in the string will currently be
escaped.
## Usage
\`\`\`Typescript
import "#elements/cards/AggregatePromiseCard";
\`\`\`
\`\`\`html
<ak-aggregate-card-promise
header="Some title"
.promise="\${somePromise}"
></ak-aggregate-card-promise>
\`\`\`
`,
},
},
},
argTypes: {
icon: { control: "text" },
label: { control: "text" },
headerLink: { control: "text" },
subtext: { control: "text" },
failureMessage: { control: "text" },
},
};
export default metadata;
const text =
"Curl up and sleep on the freshly laundered towels mew, but make meme, make cute face growl at dogs in my sleep. Scratch me there, elevator butt humans, humans, humans oh how much they love us felines we are the center of attention they feed, they clean hopped up on catnip mice. Kitty time flop over, for see owner, run in terror";
const MILLIS_PER_SECOND = 1000;
const EXAMPLE_TIMEOUT = 8000; // 8 seconds
export const DefaultStory: StoryObj = {
args: {
icon: undefined,
header: "Default",
headerLink: undefined,
subtext: `Demo has a ${EXAMPLE_TIMEOUT / MILLIS_PER_SECOND} second delay until resolution`,
},
render: ({ icon, label, headerLink, subtext }: IAggregatePromiseCard) => {
const runThis = (timeout: number, value: string) =>
new Promise((resolve) => setTimeout(resolve, timeout, value));
return html`>
<style>
ak-aggregate-card-promise {
display: inline-block;
width: 32rem;
max-width: 32rem;
}
</style>
<ak-aggregate-card-promise
label=${ifPresent(label)}
headerLink=${ifPresent(headerLink)}
subtext=${ifPresent(subtext)}
icon=${ifPresent(icon)}
.promise=${runThis(EXAMPLE_TIMEOUT, text)}
>
</ak-aggregate-card-promise> `;
},
};
export const PromiseRejected: StoryObj = {
args: {
icon: undefined,
header: "Default",
headerLink: undefined,
subtext: `Demo has a ${EXAMPLE_TIMEOUT / MILLIS_PER_SECOND} second delay until resolution`,
failureMessage: undefined,
},
render: ({ icon, label, headerLink, subtext, failureMessage }: IAggregatePromiseCard) => {
const runThis = (timeout: number, value: string) =>
new Promise((_resolve, reject) => setTimeout(reject, timeout, value));
return html`
<style>
ak-aggregate-card-promise {
display: inline-block;
width: 32rem;
max-width: 32rem;
}
</style>
<ak-aggregate-card-promise
label=${ifPresent(label)}
headerLink=${ifPresent(headerLink)}
subtext=${ifPresent(subtext)}
icon=${ifPresent(icon)}
failureMessage=${ifPresent(failureMessage)}
.promise=${runThis(EXAMPLE_TIMEOUT, text)}
>
</ak-aggregate-card-promise>
`;
},
};

View File

@@ -43,19 +43,23 @@ export const Prefix = {
export type Prefix = (typeof Prefix)[keyof typeof Prefix];
type WrappedPropertyDeclaration = PropertyDeclaration<unknown, unknown> & { wrapped?: boolean };
/**
* Given a Lit property declaration, determine the appropriate prefix for rendering the property as either a property or an attribute, based on the declaration's type and attribute configuration.
*
* @param propDeclaration The Lit property declaration to analyze.
* @returns The determined prefix for rendering the property.
*/
function resolvePrefix<T extends PropertyDeclaration<unknown, unknown>>(
propDeclaration: T,
): Prefix {
function resolvePrefix<T extends WrappedPropertyDeclaration>(propDeclaration: T): Prefix {
if (!propDeclaration.attribute) {
return Prefix.Property;
}
if ("wrapped" in propDeclaration && propDeclaration.wrapped && !propDeclaration.type) {
return Prefix.Attribute;
}
switch (propDeclaration.type) {
case String:
return Prefix.Attribute;
@@ -71,7 +75,7 @@ function resolvePrefix<T extends PropertyDeclaration<unknown, unknown>>(
* determine the appropriate name to use for rendering the property,
* taking into account any custom attribute name specified in the declaration.
*/
function resolvePropertyName<T extends PropertyDeclaration<unknown, unknown>>(
function resolvePropertyName<T extends WrappedPropertyDeclaration>(
propDeclaration: T,
prefix: Prefix,
key: string,
@@ -148,9 +152,7 @@ export function StrictUnsafe<T extends string>(
if (propDeclaration) {
const prefix = resolvePrefix(propDeclaration);
const name = resolvePropertyName(propDeclaration, prefix, propName);
filteredProps[`${prefix}${name}`] = propValue;
continue;
}

View File

@@ -7,8 +7,7 @@ ak-flow-executor.style-scope {
flex-flow: column nowrap;
}
.inspector-toggle,
.inspector-toggle.style-scope {
ak-flow-inspector-button {
position: absolute;
inset-inline-end: var(--pf-global--spacer--md);
inset-block-start: var(--pf-global--spacer--md);

View File

@@ -1,19 +1,13 @@
import "#flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import "#elements/LoadingOverlay";
import "#elements/locale/ak-locale-select";
import "#flow/components/ak-brand-footer";
import "#flow/components/ak-flow-card";
import "#flow/sources/apple/AppleLoginInit";
import "#flow/sources/plex/PlexLoginInit";
import "#flow/sources/telegram/TelegramLogin";
import "#flow/stages/FlowErrorStage";
import "#flow/stages/FlowFrameStage";
import "#flow/stages/RedirectStage";
import "#flow/inspector/FlowInspectorButton";
import Styles from "./FlowExecutor.css" with { type: "bundled-text" };
import { DEFAULT_CONFIG } from "#common/api/config";
import { parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
import { globalAK } from "#common/global";
import { configureSentry } from "#common/sentry/index";
import { applyBackgroundImageProperty } from "#common/theme";
@@ -24,36 +18,35 @@ import { listen } from "#elements/decorators/listen";
import { Interface } from "#elements/Interface";
import { showAPIErrorMessage } from "#elements/messages/MessageContainer";
import { WithBrandConfig } from "#elements/mixins/branding";
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
import { LitPropertyRecord, SlottedTemplateResult } from "#elements/types";
import { exportParts } from "#elements/utils/attributes";
import { ThemedImage } from "#elements/utils/images";
import { AKFlowAdvanceEvent, AKFlowInspectorChangeEvent } from "#flow/events";
import { AKFlowAdvanceEvent } from "#flow/events";
import { StageMapping } from "#flow/FlowExecutorStageFactory";
import { BaseStage } from "#flow/stages/base";
import type { StageHost, SubmitOptions } from "#flow/types";
import { ConsoleLogger } from "#logger/browser";
import {
CapabilitiesEnum,
ChallengeTypes,
ContextualFlowInfo,
FlowChallengeResponseRequest,
FlowErrorChallenge,
FlowLayoutEnum,
FlowsApi,
ShellChallenge,
} from "@goauthentik/api";
import { spread } from "@open-wc/lit-helpers";
import { match, P } from "ts-pattern";
import { msg } from "@lit/localize";
import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { CSSResult, html, nothing, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
import { guard } from "lit/directives/guard.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { until } from "lit/directives/until.js";
import { html as staticHTML, unsafeStatic } from "lit/static-html.js";
import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
@@ -83,10 +76,7 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
* @part locale-select-select - The select element of the locale select component.
*/
@customElement("ak-flow-executor")
export class FlowExecutor
extends WithCapabilitiesConfig(WithBrandConfig(Interface))
implements StageHost
{
export class FlowExecutor extends WithBrandConfig(Interface) implements StageHost {
public static readonly DefaultLayout: FlowLayoutEnum =
globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
@@ -109,57 +99,31 @@ export class FlowExecutor
@property({ type: String, attribute: "slug", useDefault: true })
public flowSlug: string = window.location.pathname.split("/")[3];
#challenge: ChallengeTypes | null = null;
@property({ attribute: false })
public set challenge(value: ChallengeTypes | null) {
const previousValue = this.#challenge;
const previousTitle = previousValue?.flowInfo?.title;
const nextTitle = value?.flowInfo?.title;
this.#challenge = value;
if (value?.flowInfo) {
this.flowInfo = value.flowInfo;
}
if (!nextTitle) {
document.title = this.brandingTitle;
} else if (nextTitle !== previousTitle) {
document.title = `${nextTitle} - ${this.brandingTitle}`;
}
this.requestUpdate("challenge", previousValue);
}
public get challenge(): ChallengeTypes | null {
return this.#challenge;
}
public challenge: ChallengeTypes | null = null;
@property({ type: Boolean })
public loading = false;
//#endregion
//#region State
#inspectorLoaded = false;
#logger = ConsoleLogger.prefix("flow-executor");
@property({ type: Boolean })
public inspectorOpen?: boolean;
@property({ type: Boolean })
public inspectorAvailable?: boolean;
@property({ type: String, attribute: "data-layout", useDefault: true, reflect: true })
public layout: FlowLayoutEnum = FlowExecutor.DefaultLayout;
@state()
public flowInfo?: ContextualFlowInfo;
//#endregion
//#region Internal State
#logger = ConsoleLogger.prefix("flow-executor");
#api: FlowsApi;
//#endregion
//#region Accessors
public get flowInfo() {
return this.challenge?.flowInfo ?? null;
}
//#region Lifecycle
constructor() {
@@ -169,14 +133,7 @@ export class FlowExecutor
WebsocketClient.connect();
const inspector = new URLSearchParams(window.location.search).get("inspector");
if (inspector === "" || inspector === "open") {
this.inspectorOpen = true;
this.inspectorAvailable = true;
} else if (inspector === "available") {
this.inspectorAvailable = true;
}
this.#api = new FlowsApi(DEFAULT_CONFIG);
window.addEventListener("message", (event) => {
const msg: {
@@ -233,7 +190,15 @@ export class FlowExecutor
WebsocketClient.close();
}
protected refresh = (): Promise<void> => {
private setFlowErrorChallenge(error: APIError) {
this.challenge = {
component: "ak-stage-flow-error",
error: pluckErrorDetail(error),
requestId: "",
} satisfies FlowErrorChallenge as ChallengeTypes;
}
protected refresh = async () => {
if (!this.flowSlug) {
this.#logger.debug("Skipping refresh, no flow slug provided");
return Promise.resolve();
@@ -241,26 +206,20 @@ export class FlowExecutor
this.loading = true;
return new FlowsApi(DEFAULT_CONFIG)
return this.#api
.flowsExecutorGet({
flowSlug: this.flowSlug,
query: window.location.search.substring(1),
})
.then((challenge) => {
this.challenge = challenge;
return !!this.challenge;
})
.catch(async (error) => {
const parsedError = await parseAPIResponseError(error);
const challenge: FlowErrorChallenge = {
component: "ak-stage-flow-error",
error: pluckErrorDetail(parsedError),
requestId: "",
};
showAPIErrorMessage(parsedError);
this.challenge = challenge as ChallengeTypes;
this.setFlowErrorChallenge(parsedError);
return false;
})
.finally(() => {
this.loading = false;
@@ -270,14 +229,8 @@ export class FlowExecutor
public async firstUpdated(changed: PropertyValues<this>): Promise<void> {
super.firstUpdated(changed);
if (this.can(CapabilitiesEnum.CanDebug)) {
this.inspectorAvailable = true;
}
this.refresh().then(() => {
if (this.inspectorOpen) {
window.dispatchEvent(new AKFlowAdvanceEvent());
}
window.dispatchEvent(new AKFlowAdvanceEvent());
});
}
@@ -285,6 +238,10 @@ export class FlowExecutor
public updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
document.title = match(this.challenge?.flowInfo?.title)
.with(P.nullish, () => this.brandingTitle)
.otherwise((title) => `${title} - ${this.brandingTitle}`);
if (changedProperties.has("challenge") && this.challenge?.flowInfo) {
this.layout = this.challenge?.flowInfo?.layout || FlowExecutor.DefaultLayout;
}
@@ -292,16 +249,6 @@ export class FlowExecutor
if (changedProperties.has("flowInfo") || changedProperties.has("activeTheme")) {
this.#synchronizeFlowInfo();
}
if (
changedProperties.has("inspectorOpen") &&
this.inspectorOpen &&
!this.#inspectorLoaded
) {
import("#flow/FlowInspector").then(() => {
this.#inspectorLoaded = true;
});
}
}
//#endregion
@@ -331,33 +278,19 @@ export class FlowExecutor
this.loading = true;
}
return new FlowsApi(DEFAULT_CONFIG)
return this.#api
.flowsExecutorSolve({
flowSlug: this.flowSlug,
query: window.location.search.substring(1),
flowChallengeResponseRequest: payload,
})
.then((challenge) => {
if (this.inspectorOpen) {
window.dispatchEvent(new AKFlowAdvanceEvent());
}
window.dispatchEvent(new AKFlowAdvanceEvent());
this.challenge = challenge;
if (this.challenge.flowInfo) {
this.flowInfo = this.challenge.flowInfo;
}
return !this.challenge.responseErrors;
})
.catch((error: unknown) => {
const challenge: FlowErrorChallenge = {
component: "ak-stage-flow-error",
error: pluckErrorDetail(error),
requestId: "",
};
this.challenge = challenge as ChallengeTypes;
.catch((error: APIError) => {
this.setFlowErrorChallenge(error);
return false;
})
.finally(() => {
@@ -367,183 +300,64 @@ export class FlowExecutor
//#region Render Challenge
protected async renderChallenge(
component: ChallengeTypes["component"],
): Promise<TemplateResult> {
const { challenge, inspectorOpen } = this;
protected async renderChallenge(challenge: ChallengeTypes) {
const stageEntry = StageMapping.registry.get(challenge.component);
const stageProps: LitPropertyRecord<BaseStage<NonNullable<typeof challenge>, unknown>> = {
".challenge": challenge!,
".host": this,
};
// The special cases!
if (!stageEntry) {
if (challenge.component === "xak-flow-shell") {
return html`${unsafeHTML(challenge.body)}`;
}
const props = {
...stageProps,
return this.renderChallengeError(
`No stage found for component: ${challenge.component}`,
);
}
const challengeProps: LitPropertyRecord<BaseStage<NonNullable<typeof challenge>, object>> =
{
".challenge": challenge,
".host": this,
};
const litParts = {
part: "challenge",
exportparts: exportParts(["additional-actions", "footer-band"], "challenge"),
};
switch (component) {
case "ak-stage-access-denied":
await import("#flow/stages/access_denied/AccessDeniedStage");
return html`<ak-stage-access-denied ${spread(props)}></ak-stage-access-denied>`;
case "ak-stage-identification":
await import("#flow/stages/identification/IdentificationStage");
return html`<ak-stage-identification ${spread(props)}></ak-stage-identification>`;
case "ak-stage-password":
await import("#flow/stages/password/PasswordStage");
return html`<ak-stage-password ${spread(props)}></ak-stage-password>`;
case "ak-stage-captcha":
await import("#flow/stages/captcha/CaptchaStage");
return html`<ak-stage-captcha ${spread(props)}></ak-stage-captcha>`;
case "ak-stage-consent":
await import("#flow/stages/consent/ConsentStage");
return html`<ak-stage-consent ${spread(props)}></ak-stage-consent>`;
case "ak-stage-dummy":
await import("#flow/stages/dummy/DummyStage");
return html`<ak-stage-dummy ${spread(props)}></ak-stage-dummy>`;
case "ak-stage-email":
await import("#flow/stages/email/EmailStage");
return html`<ak-stage-email ${spread(props)}></ak-stage-email>`;
case "ak-stage-autosubmit":
await import("#flow/stages/autosubmit/AutosubmitStage");
return html`<ak-stage-autosubmit ${spread(props)}></ak-stage-autosubmit>`;
case "ak-stage-prompt":
await import("#flow/stages/prompt/PromptStage");
return html`<ak-stage-prompt ${spread(props)}></ak-stage-prompt>`;
case "ak-stage-authenticator-totp":
await import("#flow/stages/authenticator_totp/AuthenticatorTOTPStage");
return html`<ak-stage-authenticator-totp
${spread(props)}
></ak-stage-authenticator-totp>`;
case "ak-stage-authenticator-duo":
await import("#flow/stages/authenticator_duo/AuthenticatorDuoStage");
return html`<ak-stage-authenticator-duo
${spread(props)}
></ak-stage-authenticator-duo>`;
case "ak-stage-authenticator-static":
await import("#flow/stages/authenticator_static/AuthenticatorStaticStage");
return html`<ak-stage-authenticator-static
${spread(props)}
></ak-stage-authenticator-static>`;
case "ak-stage-authenticator-webauthn":
return html`<ak-stage-authenticator-webauthn
${spread(props)}
></ak-stage-authenticator-webauthn>`;
case "ak-stage-authenticator-email":
await import("#flow/stages/authenticator_email/AuthenticatorEmailStage");
return html`<ak-stage-authenticator-email
${spread(props)}
></ak-stage-authenticator-email>`;
case "ak-stage-authenticator-sms":
await import("#flow/stages/authenticator_sms/AuthenticatorSMSStage");
return html`<ak-stage-authenticator-sms
${spread(props)}
></ak-stage-authenticator-sms>`;
case "ak-stage-authenticator-validate":
await import("#flow/stages/authenticator_validate/AuthenticatorValidateStage");
return html`<ak-stage-authenticator-validate
${spread(props)}
></ak-stage-authenticator-validate>`;
case "ak-stage-user-login":
await import("#flow/stages/user_login/UserLoginStage");
return html`<ak-stage-user-login
.host=${this as StageHost}
.challenge=${this.challenge}
></ak-stage-user-login>`;
case "ak-stage-endpoint-agent":
await import("#flow/stages/endpoint/agent/EndpointAgentStage");
return html`<ak-stage-endpoint-agent
.host=${this as StageHost}
.challenge=${this.challenge}
></ak-stage-endpoint-agent>`;
// Sources
case "ak-source-plex":
return html`<ak-flow-source-plex ${spread(props)}></ak-flow-source-plex>`;
case "ak-source-oauth-apple":
return html`<ak-flow-source-oauth-apple
${spread(props)}
></ak-flow-source-oauth-apple>`;
case "ak-source-telegram":
return html`<ak-flow-source-telegram ${spread(props)}></ak-flow-source-telegram>`;
// Providers
case "ak-provider-oauth2-device-code":
await import("#flow/providers/oauth2/DeviceCode");
return html`<ak-flow-provider-oauth2-code
${spread(props)}
></ak-flow-provider-oauth2-code>`;
case "ak-provider-oauth2-device-code-finish":
await import("#flow/providers/oauth2/DeviceCodeFinish");
return html`<ak-flow-provider-oauth2-code-finish
${spread(props)}
></ak-flow-provider-oauth2-code-finish>`;
case "ak-stage-session-end":
await import("#flow/providers/SessionEnd");
return html`<ak-stage-session-end ${spread(props)}></ak-stage-session-end>`;
case "ak-provider-saml-native-logout":
await import("#flow/providers/saml/NativeLogoutStage");
return html`<ak-provider-saml-native-logout
${spread(props)}
></ak-provider-saml-native-logout>`;
case "ak-provider-iframe-logout":
await import("#flow/providers/IFrameLogoutStage");
return html`<ak-provider-iframe-logout
${spread(props)}
></ak-provider-iframe-logout>`;
// Internal stages
case "ak-stage-flow-error":
return html`<ak-stage-flow-error ${spread(props)}></ak-stage-flow-error>`;
case "xak-flow-redirect":
return html`<ak-stage-redirect ${spread(props)} ?promptUser=${inspectorOpen}>
</ak-stage-redirect>`;
case "xak-flow-shell":
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
case "xak-flow-frame":
return html`<xak-flow-frame
.host=${this}
.challenge=${challenge}
></xak-flow-frame>`;
default:
return html`Invalid native challenge element`;
let mapping: StageMapping;
try {
mapping = await StageMapping.from(stageEntry);
} catch (error: unknown) {
return this.renderChallengeError(error);
}
const { tag, variant } = mapping;
const props = spread(
match(variant)
.with("challenge", () => challengeProps)
.with("standard", () => ({ ...challengeProps, ...litParts }))
.exhaustive(),
);
return staticHTML`<${unsafeStatic(tag)} ${props}></${unsafeStatic(tag)}>`;
}
//#endregion
protected renderChallengeError(error: unknown): SlottedTemplateResult {
const detail = pluckErrorDetail(error);
//#region Render Inspector
// eslint-disable-next-line no-console
console.trace(error);
@listen(AKFlowInspectorChangeEvent)
protected toggleInspector = () => {
this.inspectorOpen = !this.inspectorOpen;
const errorChallenge: FlowErrorChallenge = {
component: "ak-stage-flow-error",
error: detail,
requestId: "",
};
const drawer = document.getElementById("flow-drawer");
if (!drawer) {
return;
}
drawer.classList.toggle("pf-m-expanded", this.inspectorOpen);
drawer.classList.toggle("pf-m-collapsed", !this.inspectorOpen);
};
protected renderInspectorButton() {
return guard([this.inspectorAvailable, this.inspectorOpen], () => {
if (!this.inspectorAvailable || this.inspectorOpen) {
return null;
}
return html`<button
aria-label=${this.inspectorOpen
? msg("Close flow inspector")
: msg("Open flow inspector")}
aria-expanded=${this.inspectorOpen ? "true" : "false"}
class="inspector-toggle pf-c-button pf-m-primary"
aria-controls="flow-inspector"
@click=${this.toggleInspector}
>
<i class="fa fa-search-plus" aria-hidden="true"></i>
</button>`;
});
return html`<ak-stage-flow-error .challenge=${errorChallenge}></ak-stage-flow-error>`;
}
//#endregion
@@ -555,7 +369,7 @@ export class FlowExecutor
}
protected renderFrameBackground(): SlottedTemplateResult {
return guard([this.layout, this.#challenge], () => {
return guard([this.layout, this.challenge], () => {
if (
this.layout !== FlowLayoutEnum.SidebarLeftFrameBackground &&
this.layout !== FlowLayoutEnum.SidebarRightFrameBackground
@@ -563,7 +377,7 @@ export class FlowExecutor
return nothing;
}
const src = this.#challenge?.flowInfo?.background;
const src = this.challenge?.flowInfo?.background;
if (!src) return nothing;
@@ -596,15 +410,17 @@ export class FlowExecutor
}
protected override render(): SlottedTemplateResult {
const { component } = this.challenge || {};
const { challenge, loading } = this;
return html`<ak-locale-select
part="locale-select"
exportparts="label:locale-select-label,select:locale-select-select"
class="pf-m-dark"
></ak-locale-select>
${this.renderFrameBackground()}
<header class="pf-c-login__header">${this.renderInspectorButton()}</header>
<header class="pf-c-login__header">
<ak-flow-inspector-button></ak-flow-inspector-button>
</header>
<main
data-layout=${this.layout}
class="pf-c-login__main"
@@ -620,10 +436,12 @@ export class FlowExecutor
themedUrls: this.brandingLogoThemedUrls,
})}
</div>
${this.loading && this.challenge
? html`<ak-loading-overlay part="loading-overlay"></ak-loading-overlay>`
: nothing}
${component ? until(this.renderChallenge(component)) : this.renderLoading()}
${loading && challenge ? html`<ak-loading-overlay></ak-loading-overlay>` : nothing}
${guard([challenge], () => {
return challenge?.component
? until(this.renderChallenge(challenge))
: this.renderLoading();
})}
</main>
${this.renderFooter()}`;
}

View File

@@ -0,0 +1,110 @@
/**
* We have several patterns for the client-side components that handle a stage. In most cases, the
* stage component-name and the client-side element-name are the same, but not always. Most stages
* need CSS-related help to be visually attractive, but "challenge" stages do not. Most stages can
* be imported as-needed, but some must be pre-loaded.
*/
import { ResolvedDefaultESModule } from "#common/modules/types";
import { DeepRequired } from "#common/types";
import { StageEntries, StageEntry } from "#flow/FlowExecutorStages";
import type {
BaseStageConstructor,
FlowChallengeComponentName,
StageModuleCallback,
} from "#flow/types";
import { match, P } from "ts-pattern";
export type { FlowChallengeComponentName, StageModuleCallback };
export const propVariants = ["standard", "challenge"] as const;
export type PropVariant = (typeof propVariants)[number];
// The first type supports "import only."
type StageEntryMetadata =
| []
| [tag: string]
| [variant: PropVariant]
| [tag: string, variant: PropVariant];
const STANDARD = propVariants[0];
const isImport = (x: unknown): x is StageModuleCallback => typeof x === "function";
const PVariant = P.when(
(x): x is PropVariant => typeof x === "string" && propVariants.includes(x as PropVariant),
);
const PTag = P.when((x): x is string => typeof x === "string" && x.includes("-"));
export class StageMappingError extends TypeError {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "StageMappingError";
}
}
/**
* Resolve a stage constructor to its custom element tag name.
*/
function resolveStageTag(module: ResolvedDefaultESModule<BaseStageConstructor>): string {
const StageConstructor = module.default;
const tag = window.customElements.getName(StageConstructor);
if (!tag) {
const error = new StageMappingError(
`Failed to load module: No client stage found for component`,
);
throw error;
}
return tag;
}
interface StageMappingInit {
token: FlowChallengeComponentName;
variant: PropVariant;
tag?: string;
}
/**
* The metadata needed to load and invoke a stage.
*/
export class StageMapping {
/**
* A mapping of server-side stage tokens to client-side custom element tags.
*
* This can be used to determine if a given stage component has a corresponding client-side stage.
*/
public static readonly registry: ReadonlyMap<FlowChallengeComponentName, StageEntry> = new Map(
StageEntries.map((entry) => [entry[0], entry]),
);
public readonly token: FlowChallengeComponentName;
public readonly variant: PropVariant;
public readonly tag: string;
protected constructor({ token, variant, tag }: DeepRequired<StageMappingInit>) {
this.token = token;
this.tag = tag;
this.variant = variant;
}
/**
* Create a `StageMapping` from a `StageEntry`.
*/
public static async from([token, ...rest]: StageEntry): Promise<StageMapping> {
const last = rest.at(-1);
const callback = isImport(last) ? last : null;
const meta = (callback ? rest.slice(0, -1) : rest) as StageEntryMetadata;
const init = match<StageEntryMetadata, StageMappingInit>(meta)
.with([], () => ({ token, variant: STANDARD }))
.with([PTag, PVariant], ([tag, variant]) => ({ token, variant, tag }))
.with([PVariant], ([variant]) => ({ token, variant }))
.with([PTag], ([tag]) => ({ token, variant: STANDARD, tag }))
.exhaustive();
const tag = init.tag || (await callback?.().then(resolveStageTag)) || token;
return new StageMapping({ ...init, tag });
}
}

View File

@@ -0,0 +1,96 @@
/**
* @file Flow executor stage definitions.
*
* @remarks
* The following imports must be imported statically, as they define web components that are used in stage definitions below.
*/
import "#flow/sources/apple/AppleLoginInit";
import "#flow/sources/plex/PlexLoginInit";
import "#flow/sources/telegram/TelegramLogin";
import "#flow/stages/FlowErrorStage";
import "#flow/stages/FlowFrameStage";
import "#flow/stages/RedirectStage";
import "#flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import type {
FlowChallengeComponentName,
PropVariant,
StageModuleCallback,
} from "#flow/FlowExecutorStageFactory";
/**
* A tuple representing the metadata for a stage entry in the stage mapping registry.
*/
// prettier-ignore
export type StageEntry =
| [token: FlowChallengeComponentName, tag: string, variant: PropVariant, import?: StageModuleCallback]
| [token: FlowChallengeComponentName, variant: PropVariant, import?: StageModuleCallback]
| [token: FlowChallengeComponentName, tag: string, import?: StageModuleCallback]
| [token: FlowChallengeComponentName, import?: StageModuleCallback];
/**
* A mapping of server-side stage tokens to client-side custom element tags, along with the variant
* of props they consume and an optional import callback for lazy-loading.
*
* @remarks
* The different ways a stage can be associated with its server-side component are listed in the
* type declaration above. The variants are meant to reduce the amount of information you have to
* provide:
*
* - If the server-side component and the client-side tag are the same, only provide the component.
* - Variants describe the attribute needs. There are only two variant: "standard" and "challenge."
* The "challenge" variant is for components that immediately issue redirects. "standard" is the
* default; you don't need to specify it.
* - If the stage needs to be live immediately, import it above. Otherwise, provide an import
* function, following the examples already provided.
*
* Variants and Tags have a single strong differentiator: Tags refer to web components and so must
* always have a dash, whereas wariants are from a limited supply of names and do not have a dash.
* The StageFactory will not get confused. If you get confused, the type-checker will explain it.
*
* The resolution of the web component tag name is: tag supplied, tag received with import, tag
* derived from component name. THIS CAN FAIL: a preloaded stage with an incongruent and non- or
* incorrectly-specified tag will result in a stage that cannot be rendered. Pre-loaded stages must
* be tested carefully.
*/
// ,---. | | , . ,---.| | |
// |---|,---|,---| |\ |,---.. . . `---.|--- ,---.,---.,---.,---. |---|,---.,---.,---.
// | || || | | \ ||---'| | | || ,---|| ||---'`---. | ||---'| |---'
// ` '`---'`---' ` `'`---'`-'-' `---'`---'`---^`---|`---'`---' ` '`---'` `---'
// `---'
// prettier-ignore
export const StageEntries: readonly StageEntry[] = [
["ak-provider-iframe-logout", () => import("#flow/providers/IFrameLogoutStage")],
["ak-provider-oauth2-device-code", () => import("#flow/providers/oauth2/DeviceCode")],
["ak-provider-oauth2-device-code-finish", () => import("#flow/providers/oauth2/DeviceCodeFinish")],
["ak-provider-saml-native-logout", () => import("#flow/providers/saml/NativeLogoutStage")],
["ak-source-oauth-apple", "ak-flow-source-oauth-apple"],
["ak-source-plex", "ak-flow-source-plex"],
["ak-source-telegram", "ak-flow-source-telegram"],
["ak-stage-access-denied", () => import("#flow/stages/access_denied/AccessDeniedStage")],
["ak-stage-authenticator-duo", () => import("#flow/stages/authenticator_duo/AuthenticatorDuoStage")],
["ak-stage-authenticator-email", () => import("#flow/stages/authenticator_email/AuthenticatorEmailStage")],
["ak-stage-authenticator-sms", () => import("#flow/stages/authenticator_sms/AuthenticatorSMSStage")],
["ak-stage-authenticator-static", () => import("#flow/stages/authenticator_static/AuthenticatorStaticStage")],
["ak-stage-authenticator-totp", () => import("#flow/stages/authenticator_totp/AuthenticatorTOTPStage")],
["ak-stage-authenticator-validate", () => import("#flow/stages/authenticator_validate/AuthenticatorValidateStage")],
["ak-stage-authenticator-webauthn"],
["ak-stage-autosubmit", () => import("#flow/stages/autosubmit/AutosubmitStage")],
["ak-stage-captcha", () => import("#flow/stages/captcha/CaptchaStage")],
["ak-stage-consent", () => import("#flow/stages/consent/ConsentStage")],
["ak-stage-dummy", () => import("#flow/stages/dummy/DummyStage")],
["ak-stage-email", () => import("#flow/stages/email/EmailStage")],
["ak-stage-endpoint-agent", "challenge", () => import("#flow/stages/endpoint/agent/EndpointAgentStage")],
["ak-stage-flow-error"],
["ak-stage-identification", () => import("#flow/stages/identification/IdentificationStage")],
["ak-stage-password", () => import("#flow/stages/password/PasswordStage")],
["ak-stage-prompt", () => import("#flow/stages/prompt/PromptStage")],
["ak-stage-session-end", () => import("#flow/providers/SessionEnd")],
["ak-stage-user-login", "challenge", () => import("#flow/stages/user_login/UserLoginStage")],
["xak-flow-frame", "challenge"],
["xak-flow-redirect", "ak-stage-redirect"],
]

View File

@@ -33,21 +33,33 @@ export class BrandLinks extends AKElement {
public links: FooterLink[] = globalAK().brand.uiFooterLinks || [];
render() {
return html`<ul aria-label=${msg("Site links")} class="pf-c-list pf-m-inline" part="list">
${map(this.links, (link) => {
const links = [
...this.links,
{
name: msg("Powered by authentik"),
href: null,
},
];
return html`<ul
aria-label=${msg("Site links")}
class="pf-c-list pf-m-inline"
part="list"
data-count=${links.length}
>
${map(links, (link, idx) => {
const children = sanitizeHTML(BrandedHTMLPolicy, link.name);
if (link.href) {
return html`<li part="list-item">
<a part="list-item-link" href=${link.href}>${children}</a>
</li>`;
}
return html`<li part="list-item">
<span>${children}</span>
return html`<li
part="list-item"
data-index=${idx}
data-kind=${link.href ? "link" : "text"}
data-track-name=${idx === 0 ? "start" : idx === links.length - 1 ? "end" : idx}
>
${link.href
? html`<a part="list-item-link" href=${link.href}>${children}</a>`
: children}
</li>`;
})}
<li part="list-item"><span>${msg("Powered by authentik")}</span></li>
</ul>`;
}
}

View File

@@ -8,7 +8,7 @@ import { AKElement } from "#elements/Base";
import { listen } from "#elements/decorators/listen";
import { AKFlowAdvanceEvent, AKFlowInspectorChangeEvent } from "#flow/events";
import Styles from "#flow/FlowInspector.css";
import Styles from "#flow/inspector/FlowInspector.css";
import { FlowInspection, FlowsApi, Stage } from "@goauthentik/api";

View File

@@ -0,0 +1,85 @@
import { AKElement } from "#elements/Base";
import { listen } from "#elements/decorators/listen";
import { CapabilitiesEnum, WithCapabilitiesConfig } from "#elements/mixins/capabilities";
import { AKFlowAdvanceEvent, AKFlowInspectorChangeEvent } from "#flow/events";
import { msg } from "@lit/localize";
import { html, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
// Custom implementation because there are rules for when to show this.
@customElement("ak-flow-inspector-button")
export class FlowInspectorButton extends WithCapabilitiesConfig(AKElement) {
public static readonly styles = [PFButton];
@property({ type: Boolean, reflect: true })
public open = false;
@state()
private available = false;
@state()
private loaded = false;
@listen(AKFlowInspectorChangeEvent)
protected _onInspectorToggle = (ev: AKFlowInspectorChangeEvent) => {
this.open = ev.open;
};
public override connectedCallback() {
super.connectedCallback();
const inspector = new URLSearchParams(window.location.search).get("inspector");
this.available = this.can(CapabilitiesEnum.CanDebug) || inspector !== undefined;
this.open = inspector === "" || inspector === "open";
}
protected toggle = () => {
this.open = !this.open;
};
public override render() {
return this.open || !this.available
? nothing
: html`<button
aria-label=${this.open ? msg("Close flow inspector") : msg("Open flow inspector")}
aria-expanded=${this.open ? "true" : "false"}
class="inspector-toggle pf-c-button pf-m-primary"
aria-controls="flow-inspector"
@click=${this.toggle}
>
<i class="fa fa-search-plus" aria-hidden="true"></i>
</button>`;
}
public override firstUpdated(changed: PropertyValues<this>) {
super.firstUpdated(changed);
if (this.open) {
window.dispatchEvent(new AKFlowAdvanceEvent());
}
}
// Only load the inspector if the user requests it. It should hydrate automatically
public override updated(changed: PropertyValues<this>) {
super.updated(changed);
if (changed.has("open") && this.open && !this.loaded) {
import("#flow/inspector/FlowInspector").then(() => {
this.loaded = true;
});
}
const drawer = document.getElementById("flow-drawer");
if (changed.has("open") && drawer) {
drawer.classList.toggle("pf-m-expanded", this.open);
drawer.classList.toggle("pf-m-collapsed", !this.open);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-flow-inspector-button": FlowInspectorButton;
}
}

View File

@@ -8,7 +8,7 @@ import { FlowChallengeResponseRequest, RedirectChallenge } from "@goauthentik/ap
import { msg } from "@lit/localize";
import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { customElement, state } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@@ -18,9 +18,6 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
@customElement("ak-stage-redirect")
export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeResponseRequest> {
@property({ type: Boolean })
promptUser = false;
@state()
startedRedirect = false;
@@ -41,6 +38,14 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
return new URL(this.challenge?.to || "", document.baseURI).toString();
}
// The current implementation expects the button and the stage to share the same DOM context,
// and the same rootNode. If that changes, this will need to be updated.
get promptUser() {
return !!(this.getRootNode() as Element | undefined)?.querySelector(
"ak-flow-inspector-button",
)?.open;
}
updated(changed: PropertyValues<this>): void {
super.updated(changed);

View File

@@ -365,13 +365,13 @@ export class IdentificationStage extends BaseStage<
const enrollmentItem = enrollUrl
? html`<div class="pf-c-login__main-footer-band-item">
${msg("Need an account?")}
<a name="enroll" href="${enrollUrl}">${msg("Sign up.")}</a>
<a href="${enrollUrl}" ouiaId="enroll">${msg("Sign up.")}</a>
</div>`
: null;
const recoveryItem = recoveryUrl
? html`<div class="pf-c-login__main-footer-band-item">
<a name="recovery" href="${recoveryUrl}"
<a href="${recoveryUrl}" ouiaId="recovery"
>${msg("Forgot username or password?")}</a
>
</div>`
@@ -450,7 +450,6 @@ export class IdentificationStage extends BaseStage<
<ak-flow-input-password
label=${msg("Password")}
input-id="ak-stage-identification-password"
required
class="pf-c-form__group"
.errors=${this.challenge?.responseErrors?.password}
?allow-show-password=${this.challenge.allowShowPassword}
@@ -510,9 +509,9 @@ export class IdentificationStage extends BaseStage<
${this.renderInput()}
${this.challenge?.passwordlessUrl
? html`<a
name="passwordless"
href=${this.challenge.passwordlessUrl}
class="pf-c-button pf-m-secondary pf-m-block"
ouiaId="passwordless"
>
${msg("Use a security key")}
</a> `

View File

@@ -46,7 +46,6 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
/>
<ak-flow-input-password
label=${msg("Password")}
required
grab-focus
class="pf-c-form__group"
.errors=${this.#errors("password")}
@@ -72,9 +71,7 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
>
<legend class="sr-only">${msg("Additional actions")}</legend>
<div class="pf-c-login__main-footer-band-item">
<a name="forgot-password" href="${this.challenge.recoveryUrl}"
>${msg("Forgot password?")}</a
>
<a href="${this.challenge.recoveryUrl}">${msg("Forgot password?")}</a>
</div>
</fieldset>`
: null}

View File

@@ -344,6 +344,10 @@
(var(--ak-c-login__footer--PaddingBlock) * 2) + (var(--pf-global--LineHeight--md) * 1rem)
);
/* Only applicable to the smallest of mobile viewports. */
max-width: 100dvw;
overflow: hidden;
color: var(--ak-c-login__footer--Color);
@media (max-width: 35rem) {

View File

@@ -63,6 +63,36 @@
--ak-c-login__footer--PaddingBlock: var(--pf-global--spacer--md);
--ak-c-login__footer--Color: var(--ak-global--BackgroundColorContrast--100);
--ak-c-login__footer--ColumnGap: min(var(--pf-global--spacer--2xl), 2dvw);
--ak-c-login__footer--RowGap: var(--pf-global--spacer--md);
--ak-c-login__footer--Display: grid;
--ak-c-login__footer--MaxWidth: var(--ak-c-login--MaxWidth);
/* Gracefully degrade to the login max width if CSS size functions are not supported. */
--ak-c-login__footer--MaxWidth: min(100dvw, var(--ak-c-login--MaxWidth));
--ak-c-login__footer--TrackMin: max-content;
--ak-c-login__footer--TrackWidth: minmax(
var(--ak-c-login__footer--TrackMin),
var(--ak-c-login__footer--TrackMax)
);
--ak-c-login__footer--ItemMaxWidth: calc(
var(--ak-c-login__footer--MaxWidth) - var(--ak-c-login__footer--ColumnGap)
);
--ak-c-login__footer--ColumnCount: 4;
--ak-c-login__footer--TrackMax: calc(
(var(--ak-c-login__footer--MaxWidth) / var(--ak-c-login__footer--ColumnCount)) -
var(--ak-c-login__footer--ColumnGap)
);
@media (width <= 35rem) {
--ak-c-login__footer--TrackWidth: 1fr;
--ak-c-login__footer__list-item--FlexBasis: 100%;
}
}
[data-theme="dark"] .pf-c-login {
@@ -93,6 +123,9 @@
--pf-c-login__main-footer-band--BackgroundColor: transparent;
--pf-c-login__footer--c-list--xl--PaddingTop: 0;
--pf-c-login__footer--PaddingLeft: var(--pf-global--spacer--lg);
--pf-c-login__footer--PaddingRight: var(--pf-global--spacer--lg);
--pf-c-login__container--PaddingLeft: 0 !important;
--pf-c-login__container--PaddingRight: 0 !important;
}
@@ -133,9 +166,45 @@
&::part(list) {
--pf-c-list--m-inline--li--MarginRight: 0;
/* 3 entries is a unique scenario where 2 columns is visually balanced. */
&[data-count="3"] {
--ak-c-login__footer--ColumnCount: 2;
}
justify-content: center;
column-gap: var(--pf-global--spacer--2xl);
row-gap: var(--pf-global--spacer--md);
column-gap: var(--ak-c-login__footer--ColumnGap);
row-gap: var(--ak-c-login__footer--RowGap);
max-width: var(--ak-c-login__footer--MaxWidth);
place-items: center;
display: var(--ak-c-login__footer--Display);
grid-template-columns: repeat(
var(--ak-c-login__footer--ColumnCount),
var(--ak-c-login__footer--TrackWidth)
);
grid-template-rows:
[header] max-content
[main] max-content
[footer];
}
[part="list-item"],
&::part(list-item) {
/* CSS grid is preferred, but if the custom CSS overrides this, default to something reasonable. */
flex: 1 1 var(--ak-c-login__footer__list-item--FlexBasis, auto);
text-align: center;
max-width: var(--ak-c-login__footer--ItemMaxWidth);
&[data-kind="text"] {
&[data-track-name="start"] {
grid-column: 1 / -1;
}
&[data-track-name="end"] {
grid-column: 1 / -1;
}
}
}
[part="list-item-link"],

View File

@@ -25,6 +25,17 @@ client_id=application_client_id&
scope=openid email my-other-scope
```
Alternatively the client id may be sent via the HTTP Authorization header:
```http
POST /application/o/device/ HTTP/1.1
Host: authentik.company
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer YXBwbGljYXRpb25fY2xpZW50X2lkOg==
scope=openid email my-other-scope
```
The response contains the following fields:
- `device_code`: Device code, which is the code kept on the device

View File

@@ -34,10 +34,6 @@ Adhering to the following guidelines will help us get your PRs merged much easie
- [docs templates](./templates/index.md)
- [integration guide template](https://integrations.goauthentik.io/applications#add-a-new-application)
:::tip
If you encounter build check fails, or issues with your local build, you might need to run `make docs-install` in order to get the latest build tools and dependencies; we do occasionally update our build tools.
:::
## Setting up a docs development environment
### Prerequisites
@@ -83,7 +79,11 @@ Run the following command to install or update the build tools for both the tech
make docs-install
```
Installs or updates the build dependencies such as Docusaurus, Prettier, and ESLint. You should run this command when you are first setting up your writing environment, and also if you encounter build check fails either when you build locally or when you push your PR to the authentik repository. Running this command will grab any new dependencies that we might have added to our build tool package.
This command installs or updates the build dependencies such as Docusaurus, Prettier, and ESLint. You should run this command when you are first setting up your writing environment, and also if you encounter build check fails either when you build locally or when you push your PR to the authentik repository. Running this command will grab any new dependencies that we might have added to our build tool package.
:::tip
If you have the [full development environment](../setup/full-dev-environment.mdx) installed you can run `make install` to get all of the latest build tools and dependencies, not just those for building documentation.
:::
## Writing or modifying technical docs

View File

@@ -126,7 +126,7 @@ make migrate
```
:::info
If you ever want to start over, use `make dev-reset` which drops and restores the authentik PostgreSQL database to the state after `make migrate`.
If you ever want to start over, use `make dev-reset` which drops and restores the authentik PostgreSQL database to the state it was after you ran after `make migrate`.
:::
## 5. Running authentik
@@ -174,6 +174,18 @@ make web-watch
When `AUTHENTIK_DEBUG` is set to `true` (the default for the development environment), the authentik server automatically reloads whenever changes are made to the code. However, due to instabilities in the reloading process of the worker, that behavior is turned off for the worker. You can enable code reloading in the worker by manually running `uv run ak worker --watch`.
## Troubleshooting
### Recovery key
If you can't login anymore or the authentication flow repeats (perhaps due to an incorrectly configured stage or a failed flow import), you can create a recovery key by running this command in your terminal:
`uv run ak create_recovery_key 10 akadmin`
Copy the generated recovery key and paste it into the URL, after the domain. For example:
`http://localhost:9000/recovery/use-token/ChFk2nJKJKJKY9OdIc8yv6RCgpGYp5rdndBhR6qHoHoJoWDdlvLuvU/`
## End-to-End (E2E) Setup
Start the E2E test services with the following command:

View File

@@ -29,7 +29,7 @@ The fundamental steps to implement initial permissions are as follows:
Because the new initial permissions object is coupled with the role (and that role is assigned to a group), the initial permissions object is applied automatically to any new objects (users or flows or any object) that the member user creates.
:::info
Typically, initial permissions are assigned to non-super-user, non-administrator roles. In this scenario, the administrator needs to verify that the user has the `Can view Admin interface` permission (which allows the user to access the Admin interface). For details, see Step 5 below.
Typically, initial permissions are assigned to non-super-user, non-administrator roles. In this scenario, the administrator needs to verify that the user has the `Can access Admin interface` permission (which allows the user to access the Admin interface). For details, see Step 5 below.
Be aware that any rights beyond viewing the Admin interface will need to be assigned as well; for example, if you want a non-administrator user to be able to create flows in the Admin interface, you need to grant those global permissions to add flows.
:::
@@ -53,6 +53,6 @@ To create a new set of initial permissions and apply them to a role, follow thes
- **Permissions**: select all permissions to add to the initial permissions object.
5. To ensure that the role to which you assign the initial permissions _also_ has access to the Admin interface, check to see if the users also need [the global permission `Can view admin interface`](./manage_permissions.md#assign-can-view-admin-interface-permissions). Furthermore, verify that the user(s) has the global permissions to add specific objects.
5. To ensure that the role to which you assign the initial permissions _also_ has access to the Admin interface, check to see if the users also need [the global permission `Can access admin interface`](./manage_permissions.md#assign-can-access-admin-interface-permissions). Furthermore, verify that the user(s) has the global permissions to add specific objects.
6. Optionally, create new users and add them to the group. Each new user added to the group will automatically have the set of permissions included within the initial permissions object.

View File

@@ -69,11 +69,11 @@ To assign or remove _global_ permissions for a role:
1. Select the permission(s) you'd like to remove.
2. Click **Delete Object Permission**.
### Assign `Can view Admin interface` permissions
### Assign `Can access admin interface` permissions
You can use a role to grant regular users, who are not superusers nor Admins, the right to view the Admin interface. This can be useful in scenarios where you have a team who needs to be able to create certain objects (flows, other users, etc) but who should not have _full_ access to the Admin interface.
To assign the `Can view Admin interface` permission to a role:
To assign the `Can access Admin interface` permission to a role:
1. Go to the Admin interface and navigate to **Directory > Role**.
2. Select a specific role by clicking on the role's name.

View File

@@ -32,7 +32,7 @@ Additionally, authentik employs _initial permissions_ to streamline the process
### Global permissions
Global permissions define coarse-grained access control. For example, a role with a global permission of "Can change Flow" can change any [flow](../../add-secure-apps/flows-stages/flow/index.md). Some permissions only make sense as global permissions, e.g. the permission to add a specific object type or whether a user [`Can view admin interface`](./manage_permissions.md#assign-can-view-admin-interface-permissions).
Global permissions define coarse-grained access control. For example, a role with a global permission of "Can change Flow" can change any [flow](../../add-secure-apps/flows-stages/flow/index.md). Some permissions only make sense as global permissions, e.g. the permission to add a specific object type or whether a user [`Can access admin interface`](./manage_permissions.md#assign-can-access-admin-interface-permissions).
### Object permissions

View File

@@ -1,11 +1,11 @@
---
title: Github
title: GitHub
tags:
- source
- github
---
Allows users to authenticate using their Github credentials by configuring GitHub as a federated identity provider via OAuth2.
Allows users to authenticate using their GitHub credentials by configuring GitHub as a federated identity provider via OAuth2.
## Preparation
@@ -14,9 +14,9 @@ The following placeholders are used in this guide:
- `authentik.company` is the FQDN of the authentik installation.
- `www.my.company` is the Homepage URL for your site
## Github configuration
## GitHub configuration
To integrate GitHub with authentik you will need to create an OAuth application in GitHub Developer Settings.
To integrate GitHub with authentik, you need to create an OAuth application in GitHub Developer Settings.
1. Log in to GitHub and open the [Developer Settings](https://github.com/settings/developers) menu.
2. Create an OAuth app by clicking on the **Register a new application** button and set the following values:
@@ -70,7 +70,7 @@ from authentik.sources.oauth.models import OAuthSource
# Set this value
accepted_org = "your_organization"
# Ensure flow is only run during oauth logins via Github
# Ensure flow is only run during OAuth logins via GitHub
if not isinstance(context['source'], OAuthSource) or context["source"].provider_type != "github":
return True
@@ -81,7 +81,7 @@ access_token = connection.access_token
# We also access the user info authentik already retrieved, to get the correct username
github_username = context["oauth_userinfo"]
# Github does not include Organizations in the userinfo endpoint, so we have to call another URL
# GitHub does not include organizations in the userinfo endpoint, so we have to call another URL
orgs_response = requests.get(
"https://api.github.com/user/orgs",
auth=(github_username["login"], access_token),

View File

@@ -141,7 +141,7 @@ ChatGPT only enables the **Manage SSO** wizard after you verify ownership of you
To verify that authentik is correctly integrated with ChatGPT, log out, then attempt to log back in by entering your email address and clicking **Continue**. You should be redirected to authentik and upon a successful login, redirected back to ChatGPT.
## References
## Resources
- [OpenAI Help - Configuring SSO for ChatGPT](https://help.openai.com/en/articles/9534785-configuring-sso-for-chatgpt)
- [OpenAI Help - SSO for ChatGPT Business - FAQ](https://help.openai.com/en/articles/11489188-sso-for-chatgpt-business-faq)

View File

@@ -115,6 +115,6 @@ LOCAL_AUTH_ENABLED="false"
To confirm that authentik is properly configured with Joplin Server, log out of Joplin and then attempt to sign in again. The login page should redirect you to authentik; after a successful authentik login you should be returned to Joplin with access to your notes.
## References
## Resources
- [Joplin Server SAML configuration](https://joplinapp.org/help/apps/server/saml/)

View File

@@ -68,7 +68,7 @@ OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
Restart mastodon-web.service
## Additional Resources
## Resources
- https://github.com/mastodon/mastodon/pull/16221
- https://forum.fedimins.net/t/sso-fuer-verschiedene-dienste/42

View File

@@ -68,6 +68,6 @@ config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth,
Restart mobilizon.service
## Additional Resources
## Resources
- https://docs.mobilizon.org/3.%20System%20administration/configure/auth/#oauth

View File

@@ -126,7 +126,7 @@ With this setup, Dovecot can also be used with other email clients that support
To verify that authentik is correctly integrated with Roundcube, first log out of Roundcube. Log in to roundcube using authentik credentials. A mailbox should open and you should be able to send and receive mail.
## References
## Resources
- [Roundcube documentation - Configuration: OAuth2](https://github.com/roundcube/roundcubemail/wiki/Configuration:-OAuth2)
- [Dovecot documentation - Open Authentication v2.0 Database](https://doc.dovecot.org/main/core/config/auth/databases/oauth2.html)

View File

@@ -99,6 +99,6 @@ If your usernames in authentik and WriteFreely are different, you might need to
To link the accounts, first log into Writefreely with local credentials, and then navigate to **Customize -->Account Settings**. In the option "Linked Accounts", click on "authentik".
## Additional Resources
## Resources
- https://writefreely.org/docs/latest/admin/config

View File

@@ -82,7 +82,7 @@ The certificate file name must match the idp identifier name you set in the conf
Remember to restart Zulip.
:::
## Additional Resources
## Resources
Please refer to the following for further information:

View File

@@ -90,7 +90,7 @@ To support the integration of DigitalOcean with authentik, you need to create a
3. Click **Edit**, expand **UI Settings**, and set **Launch URL** to the **SSO sign-in URL** copied from the DigitalOcean control panel.
4. Click **Update**.
## References
## Resources
- [DigitalOcean Documentation - How to Configure Single Sign-On for Teams](https://docs.digitalocean.com/platform/teams/how-to/configure-sso/)

View File

@@ -61,7 +61,7 @@ To verify that authentik is properly integrated with OVHcloud, first log out of
Youll be redirected to your authentik instance to complete authentication. Once successful, youll be logged in to OVHcloud.
## References
## Resources
- [OVHcloud Help Center - User management & Federation](https://help.ovhcloud.com/csm/en-ie-documentation-manage-operate-user-federation?id=kb_browse_cat&kb_id=3d4a8129a884a950f07829d7d5c75243&kb_category=21734cbe50d47d90476b12dfd60b3542&spa=1)
- [OVHcloud US Help Center - User management & Federation](https://support.us.ovhcloud.com/hc/en-us/sections/27230986868883-Federation)

View File

@@ -153,7 +153,7 @@ Configure Snipe-IT SAML settings by going to settings (the gear icon), and selec
All other field can be left blank.
## Additional Resources
## Resources
- https://snipe-it.readme.io/docs/ldap-sync-login
- https://snipe-it.readme.io/docs/saml

View File

@@ -76,7 +76,7 @@ OIDC_SCOPES="openid email profile"
Then restart Arcane to apply the changes.
## References
## Resources
- [Arcane Docs - OIDC Single Sign-On](https://getarcane.app/docs/configuration/sso)

View File

@@ -104,7 +104,7 @@ Please note that by default, sssd returns all user accounts; active and disabled
:::
## Additional Resources
## Resources
The setup of sssd might vary based on Linux distribution and version; here are some resources that can help you get this set up:

View File

@@ -85,6 +85,6 @@ To support the integration of TrueCommand with authentik, you need to create an
- SAML Identity Provider URL: `Paste the Metadata URL from your clipboard.`
- Click _Save_, then click _Configure_ again then select _Start the SAML service_, then click _Save_ to start the service.
## Additional Resources
## Resources
- https://www.truenas.com/docs/truecommand/administration/settings/samlad/

View File

@@ -4,6 +4,9 @@ sidebar_label: Zammad
support_level: community
---
import TabItem from "@theme/TabItem";
import Tabs from "@theme/Tabs";
## What is Zammad
> Zammad is a web-based, open source user support/ticketing solution.
@@ -22,6 +25,18 @@ The following placeholders are used in this guide:
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## Configuration methods
There are two ways to configure single sign-on for Zammad; SAML or OIDC.
<Tabs
defaultValue="saml"
values={[
{ label: "Log in with SAML", value: "saml" },
{ label: "Log in with OIDC", value: "oidc" },
]}>
<TabItem value="saml">
## authentik configuration
To support the integration of Zammad with authentik, you need to create an application/provider pair in authentik.
@@ -30,19 +45,18 @@ To support the integration of Zammad with authentik, you need to create an appli
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings. Take note of the **slug** as it will be required later.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set the **ACS URL** `bd>https://zammad.company/auth/saml/callback`.
- Set the **Issuer** to `https://zammad.company/auth/saml/metadata`.
- Set the **Audience** to `https://zammad.company/auth/saml/metadata`.
- Set the **Service Provider Binding** to `Post`.
- Set the **SLS URL** to `https://zammad.company/auth/saml/slo`.
- Set the **SLS Binding** to `Redirect`.
- Set the **Logout Method** to `Front-channel (Iframe)`.
- Under **Advanced protocol settings**, select an available **Signing certificate**.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings. Take note of the **slug** as it will be required later.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set the **ACS URL** to `https://zammad.company/auth/saml/callback`.
- Set the **Issuer** to `https://zammad.company/auth/saml/metadata`.
- Set the **Audience** to `https://zammad.company/auth/saml/metadata`.
- Set the **Service Provider Binding** to `Post`.
- Set the **SLS URL** to `https://zammad.company/auth/saml/slo`.
- Set the **SLS Binding** to `Redirect`.
- Set the **Logout Method** to `Front-channel (Iframe)`.
- Under **Advanced protocol settings**, select an available **Signing certificate**.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
@@ -56,16 +70,68 @@ To support the integration of Zammad with authentik, you need to create an appli
To configure Zammad's integration with authentik, go to **Settings** (the gear icon) and select **Security** > **Third-party Applications**. Next, activate the **Authentication via SAML** toggle and change the following fields:
1. Set the following fields:
- **Display name**: authentik
- **IDP SSO target URL**: `https://authentik.company/application/saml/<application_slug>/sso/binding/post/`
- **IDP SSO target URL**: `https://authentik.company/application/saml/<application_slug>/sso/binding/redirect/`
- **IDP single logout target URL**: `https://authentik.company/application/saml/<application_slug>/slo/binding/redirect/`
- **IDP Certificate**: paste the contents of your certificate file.
- **IDP certificate fingerprint**: Leave this empty.
- **Name Identifier Format**: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`
- **Automatic account link on initial logon**: Enable this to automatically create Zammad users when they sign in using authentik for the first time.
2. Click **Submit** to save the authentication settings.
- **IDP Certificate**: paste the contents of your certificate file.
- **IDP certificate fingerprint**: Leave this empty.
- **Name Identifier Format**: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`
- **Automatic account link on initial logon**: Enable this to automatically create Zammad users when they sign in using authentik for the first time.
:::info
The **SSL verification** can fail when Zammad tries to connect to authentik directly, while accessing authentik in your browser works perfectly fine. You may have to disable the verification in order to save the configuration. See https://github.com/zammad/zammad/issues/5225 for details.
:::
## Additional Resources
</TabItem>
- https://admin-docs.zammad.org/en/latest/settings/security/third-party/saml.html
- https://community.zammad.org/t/saml-authentication-with-authentik-saml-login-url-and-auto-assign-permission/10876/3
<TabItem value="oidc">
## authentik configuration
To support the integration of Zammad with authentik, you need to create an application/provider pair in authentik.
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set the **Client type** to `Public`.
- Take note of the **Client ID** and **slug** values because they will be required later.
- Set the **Redirect URIs/Origins** to `Strict` / `https://zammad.company/auth/openid_connect/callback`.
- Select a **Signing Key**.
- Under **Advanced protocol settings**, set **Subject mode** to **Based on the User's Email**.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## Zammad configuration
To configure Zammad's integration with authentik, go to **Settings** (the gear icon) and select **Security** > **Third-party Applications**. Next, activate the **Authentication via OpenID Connect** toggle and change the following fields:
1. Set the following fields:
- **Display name**: authentik
- **Identifier**: the **Client ID** from above.
- **Issuer**: `https://authentik.company/application/o/<application_slug>/`
- **PKCE**: set to **yes**.
2. Click **Submit** to save the authentication settings.
At the very top of the **Third-party Applications** page are a few additional settings:
- **Automatic account link on initial logon**: Enable this to automatically link existing Zammad users when they sign in using authentik for the first time.
</TabItem>
</Tabs>
## Configuration verification
To verify that authentik is correctly integrated with Zammad, log out of Zammad and then log back in by clicking the SAML or OIDC button on the login screen. The button will show the **Display Name** you specified above. You should be redirected to authentik to log in, and if the process is successful, you'll be logged in to the Zammad dashboard.
## Resources
- [Zammad Admin Documentation - SAML](https://admin-docs.zammad.org/en/latest/settings/security/third-party/saml.html)
- [Zammad Admin Documentation - OpenID Connect](https://admin-docs.zammad.org/en/latest/settings/security/third-party/openid-connect.html)

View File

@@ -69,6 +69,6 @@ To bypass SSO for troubleshooting, navigate to `https://audiobookshelf.company/l
To confirm that authentik is properly configured with Audiobookshelf, log out and attempt to log back in using OpenID Connect. You should be redirected to authentik for authentication and then redirected back to Audiobookshelf.
## References
## Resources
- [Audiobookshelf OIDC Authentication documentation](https://www.audiobookshelf.org/guides/oidc_authentication/)

View File

@@ -101,6 +101,6 @@ If you're already signed in, go to **User Settings** > **Security** and click **
To confirm that authentik is properly configured with ezBookkeeping, log out of ezBookkeeping, click **Log in with authentik**, and complete the authentik sign-in flow. A successful authentication should return you to ezBookkeeping with access to your account.
## References
## Resources
- [ezBookkeeping Documentation - Configuration](https://ezbookkeeping.mayswind.net/configuration#authentication)

View File

@@ -80,6 +80,6 @@ Click on the user whose role should be increased from **Pending** to at least **
More details on how to administer Open WebUI can be found here: `https://docs.openwebui.com/`.
:::
## References
## Resources
- [Open WebUI Documentation - Federated Authentication Support](https://docs.openwebui.com/features/sso/)

View File

@@ -56,11 +56,11 @@ OIDC_CLIENT_SECRET=<Your Client Secret from authentik>
```yaml showLineNumbers title="config.yaml"
security:
oidc:
issuer-url: https://authentik.company/application/o/<application_slug>/
client-id: $\{OIDC_CLIENT_ID}
client-secret: $\{OIDC_CLIENT_SECRET}
redirect-url: https://gatus.company/authorization-code/callback
scopes: [openid]
issuer-url: "https://authentik.company/application/o/<application_slug>/"
client-id: "OIDC_CLIENT_ID"
client-secret: "OIDC_CLIENT_SECRET"
redirect-url: "https://gatus.company/authorization-code/callback"
scopes: ["openid"]
```
## Configuration verification

View File

@@ -67,6 +67,6 @@ To hide the local login form and show only SSO, set `PULSE_AUTH_HIDE_LOCAL_LOGIN
To confirm that authentik is properly configured with Pulse, log out and attempt to log back in using Single Sign-On. You should be redirected to authentik for authentication and then redirected back to Pulse.
## References
## Resources
- [Pulse OIDC Single Sign-On documentation](https://github.com/rcourtman/Pulse/blob/main/docs/OIDC.md)

View File

@@ -58,7 +58,7 @@ To support the integration of Wazuh with authentik, you need to create a group,
- **Application**: provide a descriptive name (e.g., `Wazuh`), an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: Select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- **ACS URL**: `https://wazuh-dashboard.company/\_opendistro/\_security/saml/acs`
- **ACS URL**: `https://wazuh-dashboard.company/_opendistro/_security/saml/acs`
- **Issuer**: `wazuh-saml`
- **Service Provider Binding**: `Post`
- Under **Advanced protocol settings**:

View File

@@ -139,7 +139,7 @@ If you encounter any issues:
- Check the FortiGate logs for SAML-related errors
:::
## Additional Resources
## Resources
- [FortiGate SSLVPN Documentation](https://docs.fortinet.com/document/fortigate/7.2.8/administration-guide/397719/ssl-vpn)
- [FortiGate SAML Configuration Guide](https://docs.fortinet.com/document/fortigate/7.2.8/administration-guide/954635/saml-sp)

View File

@@ -66,7 +66,7 @@ If you are developing Drupal locally with DDEV and authentik is also running loc
TODO
## Additional Resources
## Resources
- [Drupal OpenID Connect Module Documentation](https://www.drupal.org/project/openid_connect)
- [Drupal User Account Settings Documentation](https://www.drupal.org/docs/user_guide/en/user-registration.html)

View File

@@ -184,7 +184,7 @@ New-MgDomainFederationConfiguration `
To confirm that authentik is properly configured with Microsoft365, log out of your Microsoft account, then attempt to log back in by visiting [Microsoft 365 Portal](https://m365.cloud.microsoft/) and clicking **Sign In**. Enter an email address in your federated domain, then click **Next**. You should be redirected to authentik and, once authenticated, redirected back to Microsoft and logged in.
## References
## Resources
- [Microsoft Learn - Use a SAML 2.0 Identity Provider for Single Sign On](https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-saml-idp)
- [Microsoft Graph PowerShell - Domain Federation Configuration](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/new-mgdomainfederationconfiguration)

View File

@@ -252,7 +252,7 @@ Salesforce requires specific SCIM attributes that are not included in the defaul
4. In the **Backchannel Providers** field, select the SCIM provider you created.
5. Click **Update** to save the application.
## References
## Resources
- [Salesforce Help - Configure SSO with Salesforce as a SAML Service Provider](https://help.salesforce.com/s/articleView?id=sf.sso_saml.htm&type=5)
- [Salesforce Help - Just-in-Time SAML Assertion Fields for Salesforce](https://help.salesforce.com/s/articleView?id=sf.sso_jit_requirements.htm&type=5)

View File

@@ -72,7 +72,7 @@ SSO_ENABLED=true
SSO_AUTHORITY=https://authentik.company/application/o/<application_slug>/
SSO_CLIENT_ID=<client_id>
SSO_CLIENT_SECRET=<client_secret>
SSO_SCOPES="openid email profile offline_access"
SSO_SCOPES=email profile offline_access
SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION=false
SSO_CLIENT_CACHE_EXPIRATION=0
SSO_ONLY=false # Set to true to disable email+master password login and require SSO
@@ -81,7 +81,7 @@ SSO_SIGNUPS_MATCH_EMAIL=true # Match first SSO login to existing account by emai
Then restart Vaultwarden to apply the changes.
## References
## Resources
- [Vaultwarden Wiki - SSO using OpenID Connect](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect)

View File

@@ -54,7 +54,7 @@ Template sentence that you can typically use here: "To confirm that authentik is
If there are more specific validation methods for the Service (e.g., clicking a button), include these instructions for clarity.
## References
## Resources
List the external sources (official docs, community articles, blogs, videos) that were used to create this guide.

View File

@@ -17,8 +17,8 @@
],
"dependencies": {
"@eslint/js": "^9.39.1",
"@goauthentik/eslint-config": "^1.1.1",
"@goauthentik/prettier-config": "^3.2.1",
"@goauthentik/eslint-config": "^1.2.1",
"@goauthentik/prettier-config": "^3.4.0",
"@goauthentik/tsconfig": "^1.0.5",
"@types/node": "^25.0.0",
"@typescript-eslint/eslint-plugin": "^8.48.0",
@@ -29,8 +29,8 @@
"netlify-redirect-parser": "^14.4.0",
"npm-run-all": "^4.1.5",
"postman-code-generators": "2.1.0",
"prettier": "^3.7.3",
"prettier-plugin-packagejson": "^2.5.20",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript-eslint": "^8.48.0"
},
"engines": {
@@ -138,7 +138,6 @@
"dependencies": {
"@docusaurus/preset-classic": "^3.9.2",
"@goauthentik/docusaurus-config": "^2.2.2",
"@iconify-json/logos": "^1.2.9",
"@types/semver": "^7.7.1",
"clsx": "^2.1.1",
"fast-glob": "^3.3.3",
@@ -382,7 +381,6 @@
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.46.0.tgz",
"integrity": "sha512-22SHEEVNjZfFWkFks3P6HilkR3rS7a6GjnCIqR22Zz4HNxdfT0FG+RE7efTcFVfLUkTTMQQybvaUcwMrHXYa7Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@algolia/client-common": "5.46.0",
"@algolia/requester-browser-xhr": "5.46.0",
@@ -538,7 +536,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -2373,7 +2370,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -2396,7 +2392,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -2506,7 +2501,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -2928,7 +2922,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -3682,7 +3675,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz",
"integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@docusaurus/babel": "3.9.2",
"@docusaurus/bundler": "3.9.2",
@@ -4002,7 +3994,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz",
"integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@docusaurus/core": "3.9.2",
"@docusaurus/logger": "3.9.2",
@@ -4271,7 +4262,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz",
"integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==",
"license": "MIT",
"peer": true,
"dependencies": {
"@docusaurus/mdx-loader": "3.9.2",
"@docusaurus/module-type-aliases": "3.9.2",
@@ -4414,7 +4404,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz",
"integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@docusaurus/logger": "3.9.2",
"@docusaurus/types": "3.9.2",
@@ -4460,7 +4449,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz",
"integrity": "sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@docusaurus/logger": "3.9.2",
"@docusaurus/utils": "3.9.2",
@@ -4801,9 +4789,9 @@
"link": true
},
"node_modules/@goauthentik/eslint-config": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@goauthentik/eslint-config/-/eslint-config-1.1.1.tgz",
"integrity": "sha512-IOCQjBvD2FeUD0m1eAVhLTYxPM5pKA7UBEbub2QQYJAm7Ny8gNIA1jVoYRomYBRTundWJa5Y6iy1zN52r6Qofw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@goauthentik/eslint-config/-/eslint-config-1.2.1.tgz",
"integrity": "sha512-Il47UJolIPG2j671iV64QcX5DCScorm70m0rjr7wsGcSDvc/CflD04fHKwXH4Ud+Hs5khJD4LQp2usTZT/Bi/g==",
"license": "MIT",
"dependencies": {
"eslint": "^9.39.1",
@@ -4815,13 +4803,13 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.47.0"
"typescript-eslint": "^8.49.0"
},
"peerDependenciesMeta": {
"react": {
@@ -4837,20 +4825,20 @@
"link": true
},
"node_modules/@goauthentik/prettier-config": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@goauthentik/prettier-config/-/prettier-config-3.2.1.tgz",
"integrity": "sha512-Cq/z0s0LRFaDVDaNvh8cZzMJ8RHE3YG+Dwi3maLA6OJkLMg/hSQF8AXneLlwo/eF4S5TORbrd0gl7l8xiUqrcQ==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@goauthentik/prettier-config/-/prettier-config-3.4.0.tgz",
"integrity": "sha512-00SnyvdfHhoifuqlQlS+Beyl0aFUUBWShI4Ci+QxFS1pkiyl1HnHbr20QRSR7DPqPmRMVAYmsr1Yv6/1heNhIg==",
"license": "MIT",
"dependencies": {
"format-imports": "^4.0.8"
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"prettier": "^3.6.2",
"prettier-plugin-packagejson": "^2.5.19"
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0"
}
},
"node_modules/@goauthentik/tsconfig": {
@@ -4970,15 +4958,6 @@
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/logos": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@iconify-json/logos/-/logos-1.2.10.tgz",
"integrity": "sha512-qxaXKJ6fu8jzTMPQdHtNxlfx6tBQ0jXRbHZIYy5Ilh8Lx9US9FsAdzZWUR8MXV8PnWTKGDFO4ZZee9VwerCyMA==",
"license": "CC0-1.0",
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/mdi": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.3.tgz",
@@ -5726,18 +5705,6 @@
"node": ">=0.10"
}
},
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@pnpm/config.env-replace": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
@@ -6038,7 +6005,6 @@
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.6.7.tgz",
"integrity": "sha512-tkd4nSzTf+pDa9OAE4INi/JEa93HNszjWy5C9+trf4ZCXLLHsHxHQFbzoreuz4Vv2PlCWajgvAdiPMV1vGIkuw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@module-federation/runtime-tools": "0.21.6",
"@rspack/binding": "1.6.7",
@@ -6283,7 +6249,6 @@
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
@@ -6388,7 +6353,6 @@
"integrity": "sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==",
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.25"
@@ -7361,7 +7325,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -7558,7 +7521,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz",
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0",
@@ -8038,7 +8000,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -8133,7 +8094,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -8198,7 +8158,6 @@
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.46.0.tgz",
"integrity": "sha512-7ML6fa2K93FIfifG3GMWhDEwT5qQzPTmoHKCTvhzGEwdbQ4n0yYUWZlLYT75WllTGJCJtNUI0C1ybN4BCegqvg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@algolia/abtesting": "1.12.0",
"@algolia/client-abtesting": "5.46.0",
@@ -8899,7 +8858,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -9287,7 +9245,6 @@
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
@@ -10121,7 +10078,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -10441,7 +10397,6 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10"
}
@@ -10851,7 +10806,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -11389,7 +11343,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -12625,7 +12578,6 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20",
@@ -13298,7 +13250,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -14699,9 +14650,9 @@
}
},
"node_modules/git-hooks-list": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.1.1.tgz",
"integrity": "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.2.1.tgz",
"integrity": "sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==",
"license": "MIT",
"funding": {
"url": "https://github.com/fisker/git-hooks-list?sponsor=1"
@@ -20804,7 +20755,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -21444,7 +21394,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -22348,7 +22297,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -22962,11 +22910,10 @@
}
},
"node_modules/prettier": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -22978,17 +22925,15 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.20",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.20.tgz",
"integrity": "sha512-G8cowPh+QmJJECTZlrPDKWkVVcwrFjF2rGcw546w3N8blLoc4szSs8UUPfFVxHUNLUjiru71Ah83g1lZkeK9Bw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-3.0.0.tgz",
"integrity": "sha512-z8/QmPSqx/ANvvQMWJSkSq1+ihBXeuwDEYdjX3ZjRJ5Ty1k7vGbFQfhzk2eDe0rwS/TNyRjWK/qnjJEStAOtDw==",
"license": "MIT",
"peer": true,
"dependencies": {
"sort-package-json": "3.5.0",
"synckit": "0.11.11"
"sort-package-json": "3.6.0"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
"prettier": "^3"
},
"peerDependenciesMeta": {
"prettier": {
@@ -23274,7 +23219,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
"integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -23294,7 +23238,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -23331,7 +23274,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz",
"integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -23392,7 +23334,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz",
"integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/react": "*"
},
@@ -24115,7 +24056,6 @@
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
"integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
@@ -24285,7 +24225,6 @@
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.9.2"
}
@@ -25015,7 +24954,6 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.96.0.tgz",
"integrity": "sha512-8u4xqqUeugGNCYwr9ARNtQKTOj4KmYiJAVKXf2CTIivTCR51j96htbMKWDru8H5SaQWpyVgTfOF8Ylyf5pun1Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@@ -25141,7 +25079,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -25927,24 +25864,24 @@
}
},
"node_modules/sort-object-keys": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.0.1.tgz",
"integrity": "sha512-R89fO+z3x7hiKPXX5P0qim+ge6Y60AjtlW+QQpRozrrNcR1lw9Pkpm5MLB56HoNvdcLHL4wbpq16OcvGpEDJIg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.1.0.tgz",
"integrity": "sha512-SOiEnthkJKPv2L6ec6HMwhUcN0/lppkeYuN1x63PbyPRrgSPIuBJCiYxYyvWRTtjMlOi14vQUCGUJqS6PLVm8g==",
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.5.0.tgz",
"integrity": "sha512-moY4UtptUuP5sPuu9H9dp8xHNel7eP5/Kz/7+90jTvC0IOiPH2LigtRM/aSFSxreaWoToHUVUpEV4a2tAs2oKQ==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.6.0.tgz",
"integrity": "sha512-fyJsPLhWvY7u2KsKPZn1PixbXp+1m7V8NWqU8CvgFRbMEX41Ffw1kD8n0CfJiGoaSfoAvbrqRRl/DcHO8omQOQ==",
"license": "MIT",
"dependencies": {
"detect-indent": "^7.0.1",
"detect-indent": "^7.0.2",
"detect-newline": "^4.0.1",
"git-hooks-list": "^4.0.0",
"git-hooks-list": "^4.1.1",
"is-plain-obj": "^4.1.0",
"semver": "^7.7.1",
"sort-object-keys": "^2.0.0",
"tinyglobby": "^0.2.12"
"semver": "^7.7.3",
"sort-object-keys": "^2.0.1",
"tinyglobby": "^0.2.15"
},
"bin": {
"sort-package-json": "cli.js"
@@ -26600,21 +26537,6 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tapable": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
@@ -26829,7 +26751,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -26995,8 +26916,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"peer": true
"license": "0BSD"
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -27144,7 +27064,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -27158,7 +27077,6 @@
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.49.0.tgz",
"integrity": "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.49.0",
"@typescript-eslint/parser": "8.49.0",
@@ -27900,7 +27818,6 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz",
"integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -28739,7 +28656,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

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