Compare commits
2 Commits
imports-fo
...
events/imp
Author | SHA1 | Date | |
---|---|---|---|
1fcef476c3 | |||
e8b6b3366b |
2
.github/workflows/ci-main-daily.yml
vendored
2
.github/workflows/ci-main-daily.yml
vendored
@ -15,8 +15,8 @@ jobs:
|
||||
matrix:
|
||||
version:
|
||||
- docs
|
||||
- version-2025-4
|
||||
- version-2025-2
|
||||
- version-2024-12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -2,7 +2,7 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, next, version*]
|
||||
branches: [main, "*", next, version*]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -6,15 +6,13 @@
|
||||
"!Context scalar",
|
||||
"!Enumerate sequence",
|
||||
"!Env scalar",
|
||||
"!Env sequence",
|
||||
"!Find sequence",
|
||||
"!Format sequence",
|
||||
"!If sequence",
|
||||
"!Index scalar",
|
||||
"!KeyOf scalar",
|
||||
"!Value scalar",
|
||||
"!AtIndex scalar",
|
||||
"!ParseJSON scalar"
|
||||
"!AtIndex scalar"
|
||||
],
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.importModuleSpecifierEnding": "index",
|
||||
|
@ -75,7 +75,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 4: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.15 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.13 AS uv
|
||||
# Stage 5: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
|
||||
|
||||
|
6
Makefile
6
Makefile
@ -150,9 +150,9 @@ gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescri
|
||||
--additional-properties=npmVersion=${NPM_VERSION} \
|
||||
--git-repo-id authentik \
|
||||
--git-user-id goauthentik
|
||||
|
||||
cd ${PWD}/${GEN_API_TS} && npm link
|
||||
cd ${PWD}/web && npm link @goauthentik/api
|
||||
mkdir -p web/node_modules/@goauthentik/api
|
||||
cd ${PWD}/${GEN_API_TS} && npm i
|
||||
\cp -rf ${PWD}/${GEN_API_TS}/* web/node_modules/@goauthentik/api
|
||||
|
||||
gen-client-py: gen-clean-py ## Build and install the authentik API for Python
|
||||
docker run \
|
||||
|
@ -37,7 +37,6 @@ entries:
|
||||
- attrs:
|
||||
attributes:
|
||||
env_null: !Env [bar-baz, null]
|
||||
json_parse: !ParseJSON '{"foo": "bar"}'
|
||||
policy_pk1:
|
||||
!Format [
|
||||
"%s-%s",
|
||||
|
@ -35,6 +35,6 @@ def blueprint_tester(file_name: Path) -> Callable:
|
||||
|
||||
|
||||
for blueprint_file in Path("blueprints/").glob("**/*.yaml"):
|
||||
if "local" in str(blueprint_file) or "testing" in str(blueprint_file):
|
||||
if "local" in str(blueprint_file):
|
||||
continue
|
||||
setattr(TestPackaged, f"test_blueprint_{blueprint_file}", blueprint_tester(blueprint_file))
|
||||
|
@ -215,7 +215,6 @@ class TestBlueprintsV1(TransactionTestCase):
|
||||
},
|
||||
"nested_context": "context-nested-value",
|
||||
"env_null": None,
|
||||
"json_parse": {"foo": "bar"},
|
||||
"at_index_sequence": "foo",
|
||||
"at_index_sequence_default": "non existent",
|
||||
"at_index_mapping": 2,
|
||||
|
@ -6,7 +6,6 @@ from copy import copy
|
||||
from dataclasses import asdict, dataclass, field, is_dataclass
|
||||
from enum import Enum
|
||||
from functools import reduce
|
||||
from json import JSONDecodeError, loads
|
||||
from operator import ixor
|
||||
from os import getenv
|
||||
from typing import Any, Literal, Union
|
||||
@ -292,22 +291,6 @@ class Context(YAMLTag):
|
||||
return value
|
||||
|
||||
|
||||
class ParseJSON(YAMLTag):
|
||||
"""Parse JSON from context/env/etc value"""
|
||||
|
||||
raw: str
|
||||
|
||||
def __init__(self, loader: "BlueprintLoader", node: ScalarNode) -> None:
|
||||
super().__init__()
|
||||
self.raw = node.value
|
||||
|
||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||
try:
|
||||
return loads(self.raw)
|
||||
except JSONDecodeError as exc:
|
||||
raise EntryInvalidError.from_entry(exc, entry) from exc
|
||||
|
||||
|
||||
class Format(YAMLTag):
|
||||
"""Format a string"""
|
||||
|
||||
@ -683,7 +666,6 @@ class BlueprintLoader(SafeLoader):
|
||||
self.add_constructor("!Value", Value)
|
||||
self.add_constructor("!Index", Index)
|
||||
self.add_constructor("!AtIndex", AtIndex)
|
||||
self.add_constructor("!ParseJSON", ParseJSON)
|
||||
|
||||
|
||||
class EntryInvalidError(SentryIgnoredException):
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""Authenticator Devices API Views"""
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.fields import (
|
||||
BooleanField,
|
||||
@ -13,7 +15,6 @@ from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
from authentik.core.api.users import ParamUserSerializer
|
||||
from authentik.core.api.utils import MetaNameSerializer
|
||||
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import EndpointDevice
|
||||
from authentik.stages.authenticator import device_classes, devices_for_user
|
||||
@ -22,7 +23,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||
|
||||
|
||||
class DeviceSerializer(MetaNameSerializer):
|
||||
"""Serializer for authenticator devices"""
|
||||
"""Serializer for Duo authenticator devices"""
|
||||
|
||||
pk = CharField()
|
||||
name = CharField()
|
||||
@ -32,27 +33,22 @@ class DeviceSerializer(MetaNameSerializer):
|
||||
last_updated = DateTimeField(read_only=True)
|
||||
last_used = DateTimeField(read_only=True, allow_null=True)
|
||||
extra_description = SerializerMethodField()
|
||||
external_id = SerializerMethodField()
|
||||
|
||||
def get_type(self, instance: Device) -> str:
|
||||
"""Get type of device"""
|
||||
return instance._meta.label
|
||||
|
||||
def get_extra_description(self, instance: Device) -> str | None:
|
||||
def get_extra_description(self, instance: Device) -> str:
|
||||
"""Get extra description"""
|
||||
if isinstance(instance, WebAuthnDevice):
|
||||
return instance.device_type.description if instance.device_type else None
|
||||
return (
|
||||
instance.device_type.description
|
||||
if instance.device_type
|
||||
else _("Extra description not available")
|
||||
)
|
||||
if isinstance(instance, EndpointDevice):
|
||||
return instance.data.get("deviceSignals", {}).get("deviceModel")
|
||||
return None
|
||||
|
||||
def get_external_id(self, instance: Device) -> str | None:
|
||||
"""Get external Device ID"""
|
||||
if isinstance(instance, WebAuthnDevice):
|
||||
return instance.device_type.aaguid if instance.device_type else None
|
||||
if isinstance(instance, EndpointDevice):
|
||||
return instance.data.get("deviceSignals", {}).get("deviceModel")
|
||||
return None
|
||||
return ""
|
||||
|
||||
|
||||
class DeviceViewSet(ViewSet):
|
||||
@ -61,6 +57,7 @@ class DeviceViewSet(ViewSet):
|
||||
serializer_class = DeviceSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(responses={200: DeviceSerializer(many=True)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Get all devices for current user"""
|
||||
devices = devices_for_user(request.user)
|
||||
@ -82,11 +79,18 @@ class AdminDeviceViewSet(ViewSet):
|
||||
yield from device_set
|
||||
|
||||
@extend_schema(
|
||||
parameters=[ParamUserSerializer],
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="user",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.INT,
|
||||
)
|
||||
],
|
||||
responses={200: DeviceSerializer(many=True)},
|
||||
)
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Get all devices for current user"""
|
||||
args = ParamUserSerializer(data=request.query_params)
|
||||
args.is_valid(raise_exception=True)
|
||||
return Response(DeviceSerializer(self.get_devices(**args.validated_data), many=True).data)
|
||||
kwargs = {}
|
||||
if "user" in request.query_params:
|
||||
kwargs = {"user": request.query_params["user"]}
|
||||
return Response(DeviceSerializer(self.get_devices(**kwargs), many=True).data)
|
||||
|
@ -90,12 +90,6 @@ from authentik.stages.email.utils import TemplateEmailMessage
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class ParamUserSerializer(PassiveSerializer):
|
||||
"""Partial serializer for query parameters to select a user"""
|
||||
|
||||
user = PrimaryKeyRelatedField(queryset=User.objects.all().exclude_anonymous(), required=False)
|
||||
|
||||
|
||||
class UserGroupSerializer(ModelSerializer):
|
||||
"""Simplified Group Serializer for user's groups"""
|
||||
|
||||
@ -407,7 +401,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
StrField(User, "path"),
|
||||
BoolField(User, "is_active", nullable=True),
|
||||
ChoiceSearchField(User, "type"),
|
||||
JSONSearchField(User, "attributes", suggest_nested=False),
|
||||
JSONSearchField(User, "attributes"),
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Model
|
||||
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
|
||||
from drf_spectacular.plumbing import build_basic_type
|
||||
@ -31,27 +30,7 @@ def is_dict(value: Any):
|
||||
raise ValidationError("Value must be a dictionary, and not have any duplicate keys.")
|
||||
|
||||
|
||||
class JSONDictField(JSONField):
|
||||
"""JSON Field which only allows dictionaries"""
|
||||
|
||||
default_validators = [is_dict]
|
||||
|
||||
|
||||
class JSONExtension(OpenApiSerializerFieldExtension):
|
||||
"""Generate API Schema for JSON fields as"""
|
||||
|
||||
target_class = "authentik.core.api.utils.JSONDictField"
|
||||
|
||||
def map_serializer_field(self, auto_schema, direction):
|
||||
return build_basic_type(OpenApiTypes.OBJECT)
|
||||
|
||||
|
||||
class ModelSerializer(BaseModelSerializer):
|
||||
|
||||
# By default, JSON fields we have are used to store dictionaries
|
||||
serializer_field_mapping = BaseModelSerializer.serializer_field_mapping.copy()
|
||||
serializer_field_mapping[models.JSONField] = JSONDictField
|
||||
|
||||
def create(self, validated_data):
|
||||
instance = super().create(validated_data)
|
||||
|
||||
@ -92,6 +71,21 @@ class ModelSerializer(BaseModelSerializer):
|
||||
return instance
|
||||
|
||||
|
||||
class JSONDictField(JSONField):
|
||||
"""JSON Field which only allows dictionaries"""
|
||||
|
||||
default_validators = [is_dict]
|
||||
|
||||
|
||||
class JSONExtension(OpenApiSerializerFieldExtension):
|
||||
"""Generate API Schema for JSON fields as"""
|
||||
|
||||
target_class = "authentik.core.api.utils.JSONDictField"
|
||||
|
||||
def map_serializer_field(self, auto_schema, direction):
|
||||
return build_basic_type(OpenApiTypes.OBJECT)
|
||||
|
||||
|
||||
class PassiveSerializer(Serializer):
|
||||
"""Base serializer class which doesn't implement create/update methods"""
|
||||
|
||||
|
@ -11,7 +11,6 @@ from authentik.core.expression.exceptions import SkipObjectException
|
||||
from authentik.core.models import User
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.expression.evaluator import BaseEvaluator
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.policies.types import PolicyRequest
|
||||
|
||||
PROPERTY_MAPPING_TIME = Histogram(
|
||||
@ -69,12 +68,11 @@ class PropertyMappingEvaluator(BaseEvaluator):
|
||||
# For dry-run requests we don't save exceptions
|
||||
if self.dry_run:
|
||||
return
|
||||
error_string = exception_to_string(exc)
|
||||
event = Event.new(
|
||||
EventAction.PROPERTY_MAPPING_EXCEPTION,
|
||||
expression=expression_source,
|
||||
message=error_string,
|
||||
)
|
||||
message="Failed to execute property mapping",
|
||||
).with_exception(exc)
|
||||
if "request" in self._context:
|
||||
req: PolicyRequest = self._context["request"]
|
||||
if req.http_request:
|
||||
|
@ -13,6 +13,7 @@ class Command(TenantCommand):
|
||||
parser.add_argument("usernames", nargs="*", type=str)
|
||||
|
||||
def handle_per_tenant(self, **options):
|
||||
print(options)
|
||||
new_type = UserTypes(options["type"])
|
||||
qs = (
|
||||
User.objects.exclude_anonymous()
|
||||
|
@ -1,8 +1,10 @@
|
||||
from hashlib import sha256
|
||||
|
||||
from django.contrib.auth.signals import user_logged_out
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.http.request import HttpRequest
|
||||
from guardian.shortcuts import assign_perm
|
||||
|
||||
from authentik.core.models import (
|
||||
@ -60,6 +62,31 @@ def ssf_providers_post_save(sender: type[Model], instance: SSFProvider, created:
|
||||
instance.save()
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def ssf_user_logged_out_session_revoked(sender, request: HttpRequest, user: User, **_):
|
||||
"""Session revoked trigger (user logged out)"""
|
||||
if not request.session or not request.session.session_key or not user:
|
||||
return
|
||||
send_ssf_event(
|
||||
EventTypes.CAEP_SESSION_REVOKED,
|
||||
{
|
||||
"initiating_entity": "user",
|
||||
},
|
||||
sub_id={
|
||||
"format": "complex",
|
||||
"session": {
|
||||
"format": "opaque",
|
||||
"id": sha256(request.session.session_key.encode("ascii")).hexdigest(),
|
||||
},
|
||||
"user": {
|
||||
"format": "email",
|
||||
"email": user.email,
|
||||
},
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
def ssf_user_session_delete_session_revoked(sender, instance: AuthenticatedSession, **_):
|
||||
"""Session revoked trigger (users' session has been deleted)
|
||||
|
@ -97,7 +97,6 @@ class SourceStageFinal(StageView):
|
||||
token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
|
||||
self.logger.info("Replacing source flow with overridden flow", flow=token.flow.slug)
|
||||
plan = token.plan
|
||||
plan.context.update(self.executor.plan.context)
|
||||
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
|
||||
response = plan.to_redirect(self.request, token.flow)
|
||||
token.delete()
|
||||
|
@ -90,17 +90,14 @@ class TestSourceStage(FlowTestCase):
|
||||
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
||||
plan.insert_stage(in_memory_stage(SourceStageFinal), index=0)
|
||||
plan.context[PLAN_CONTEXT_IS_RESTORED] = flow_token
|
||||
plan.context["foo"] = "bar"
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
# Pretend we've just returned from the source
|
||||
with self.assertFlowFinishes() as ff:
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageRedirects(
|
||||
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||
)
|
||||
self.assertEqual(ff().context["foo"], "bar")
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageRedirects(
|
||||
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||
)
|
||||
|
@ -19,8 +19,8 @@ from authentik.blueprints.v1.importer import excluded_models
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.events.models import Event, EventAction, Notification
|
||||
from authentik.events.utils import model_to_dict
|
||||
from authentik.lib.sentry import should_ignore_exception
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.sentry import before_send
|
||||
from authentik.lib.utils.errors import exception_to_dict
|
||||
from authentik.stages.authenticator_static.models import StaticToken
|
||||
|
||||
IGNORED_MODELS = tuple(
|
||||
@ -170,14 +170,16 @@ class AuditMiddleware:
|
||||
thread = EventNewThread(
|
||||
EventAction.SUSPICIOUS_REQUEST,
|
||||
request,
|
||||
message=exception_to_string(exception),
|
||||
message=str(exception),
|
||||
exception=exception_to_dict(exception),
|
||||
)
|
||||
thread.run()
|
||||
elif not should_ignore_exception(exception):
|
||||
elif before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
||||
thread = EventNewThread(
|
||||
EventAction.SYSTEM_EXCEPTION,
|
||||
request,
|
||||
message=exception_to_string(exception),
|
||||
message=str(exception),
|
||||
exception=exception_to_dict(exception),
|
||||
)
|
||||
thread.run()
|
||||
|
||||
|
@ -38,6 +38,7 @@ from authentik.events.utils import (
|
||||
)
|
||||
from authentik.lib.models import DomainlessURLValidator, SerializerModel
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.errors import exception_to_dict
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
@ -163,6 +164,12 @@ class Event(SerializerModel, ExpiringModel):
|
||||
event = Event(action=action, app=app, context=cleaned_kwargs)
|
||||
return event
|
||||
|
||||
def with_exception(self, exc: Exception) -> "Event":
|
||||
"""Add data from 'exc' to the event in a database-saveable format"""
|
||||
self.context.setdefault("message", str(exc))
|
||||
self.context["exception"] = exception_to_dict(exc)
|
||||
return self
|
||||
|
||||
def set_user(self, user: User) -> "Event":
|
||||
"""Set `.user` based on user, ensuring the correct attributes are copied.
|
||||
This should only be used when self.from_http is *not* used."""
|
||||
@ -193,32 +200,17 @@ class Event(SerializerModel, ExpiringModel):
|
||||
brand: Brand = request.brand
|
||||
self.brand = sanitize_dict(model_to_dict(brand))
|
||||
if hasattr(request, "user"):
|
||||
self.user = get_user(request.user)
|
||||
original_user = None
|
||||
if hasattr(request, "session"):
|
||||
original_user = request.session.get(SESSION_KEY_IMPERSONATE_ORIGINAL_USER, None)
|
||||
self.user = get_user(request.user, original_user)
|
||||
if user:
|
||||
self.user = get_user(user)
|
||||
# Check if we're currently impersonating, and add that user
|
||||
if hasattr(request, "session"):
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
|
||||
# Check if we're currently impersonating, and add that user
|
||||
if SESSION_KEY_IMPERSONATE_ORIGINAL_USER in request.session:
|
||||
self.user = get_user(request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER])
|
||||
self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER])
|
||||
# Special case for events that happen during a flow, the user might not be authenticated
|
||||
# yet but is a pending user instead
|
||||
if SESSION_KEY_PLAN in request.session:
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
||||
|
||||
plan: FlowPlan = request.session[SESSION_KEY_PLAN]
|
||||
pending_user = plan.context.get(PLAN_CONTEXT_PENDING_USER, None)
|
||||
# Only save `authenticated_as` if there's a different pending user in the flow
|
||||
# than the user that is authenticated
|
||||
if pending_user and (
|
||||
(pending_user.pk and pending_user.pk != self.user.get("pk"))
|
||||
or (not pending_user.pk)
|
||||
):
|
||||
orig_user = self.user.copy()
|
||||
|
||||
self.user = {"authenticated_as": orig_user, **get_user(pending_user)}
|
||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
||||
self.client_ip = ClientIPMiddleware.get_client_ip(request)
|
||||
# Enrich event data
|
||||
|
@ -127,8 +127,8 @@ class SystemTask(TenantTask):
|
||||
)
|
||||
Event.new(
|
||||
EventAction.SYSTEM_TASK_EXCEPTION,
|
||||
message=f"Task {self.__name__} encountered an error: {exception_to_string(exc)}",
|
||||
).save()
|
||||
message=f"Task {self.__name__} encountered an error",
|
||||
).with_exception(exc).save()
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.events.context_processors.base import get_context_processors
|
||||
from authentik.events.context_processors.geoip import GeoIPContextProcessor
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
|
||||
class TestGeoIP(TestCase):
|
||||
@ -15,7 +13,8 @@ class TestGeoIP(TestCase):
|
||||
|
||||
def test_simple(self):
|
||||
"""Test simple city wrapper"""
|
||||
# IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json
|
||||
# IPs from
|
||||
# https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json
|
||||
self.assertEqual(
|
||||
self.reader.city_dict("2.125.160.216"),
|
||||
{
|
||||
@ -26,12 +25,3 @@ class TestGeoIP(TestCase):
|
||||
"long": -1.25,
|
||||
},
|
||||
)
|
||||
|
||||
def test_special_chars(self):
|
||||
"""Test city name with special characters"""
|
||||
# IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json
|
||||
event = Event.new(EventAction.LOGIN)
|
||||
event.client_ip = "89.160.20.112"
|
||||
for processor in get_context_processors():
|
||||
processor.enrich_event(event)
|
||||
event.save()
|
||||
|
@ -8,11 +8,9 @@ from django.views.debug import SafeExceptionReporterFilter
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tests.utils import create_test_user
|
||||
from authentik.core.models import Group
|
||||
from authentik.events.models import Event
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
||||
from authentik.flows.views.executor import QS_QUERY, SESSION_KEY_PLAN
|
||||
from authentik.flows.views.executor import QS_QUERY
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
|
||||
@ -118,92 +116,3 @@ class TestEvents(TestCase):
|
||||
"pk": brand.pk.hex,
|
||||
},
|
||||
)
|
||||
|
||||
def test_from_http_flow_pending_user(self):
|
||||
"""Test request from flow request with a pending user"""
|
||||
user = create_test_user()
|
||||
|
||||
session = self.client.session
|
||||
plan = FlowPlan(generate_id())
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = user
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
request = self.factory.get("/")
|
||||
request.session = session
|
||||
request.user = user
|
||||
|
||||
event = Event.new("unittest").from_http(request)
|
||||
self.assertEqual(
|
||||
event.user,
|
||||
{
|
||||
"email": user.email,
|
||||
"pk": user.pk,
|
||||
"username": user.username,
|
||||
},
|
||||
)
|
||||
|
||||
def test_from_http_flow_pending_user_anon(self):
|
||||
"""Test request from flow request with a pending user"""
|
||||
user = create_test_user()
|
||||
anon = get_anonymous_user()
|
||||
|
||||
session = self.client.session
|
||||
plan = FlowPlan(generate_id())
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = user
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
request = self.factory.get("/")
|
||||
request.session = session
|
||||
request.user = anon
|
||||
|
||||
event = Event.new("unittest").from_http(request)
|
||||
self.assertEqual(
|
||||
event.user,
|
||||
{
|
||||
"authenticated_as": {
|
||||
"pk": anon.pk,
|
||||
"is_anonymous": True,
|
||||
"username": "AnonymousUser",
|
||||
"email": "",
|
||||
},
|
||||
"email": user.email,
|
||||
"pk": user.pk,
|
||||
"username": user.username,
|
||||
},
|
||||
)
|
||||
|
||||
def test_from_http_flow_pending_user_fake(self):
|
||||
"""Test request from flow request with a pending user"""
|
||||
user = User(
|
||||
username=generate_id(),
|
||||
email=generate_id(),
|
||||
)
|
||||
anon = get_anonymous_user()
|
||||
|
||||
session = self.client.session
|
||||
plan = FlowPlan(generate_id())
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = user
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
request = self.factory.get("/")
|
||||
request.session = session
|
||||
request.user = anon
|
||||
|
||||
event = Event.new("unittest").from_http(request)
|
||||
self.assertEqual(
|
||||
event.user,
|
||||
{
|
||||
"authenticated_as": {
|
||||
"pk": anon.pk,
|
||||
"is_anonymous": True,
|
||||
"username": "AnonymousUser",
|
||||
"email": "",
|
||||
},
|
||||
"email": user.email,
|
||||
"pk": user.pk,
|
||||
"username": user.username,
|
||||
},
|
||||
)
|
||||
|
@ -74,8 +74,8 @@ def model_to_dict(model: Model) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def get_user(user: User | AnonymousUser) -> dict[str, Any]:
|
||||
"""Convert user object to dictionary"""
|
||||
def get_user(user: User | AnonymousUser, original_user: User | None = None) -> dict[str, Any]:
|
||||
"""Convert user object to dictionary, optionally including the original user"""
|
||||
if isinstance(user, AnonymousUser):
|
||||
try:
|
||||
user = get_anonymous_user()
|
||||
@ -88,6 +88,10 @@ def get_user(user: User | AnonymousUser) -> dict[str, Any]:
|
||||
}
|
||||
if user.username == settings.ANONYMOUS_USER_NAME:
|
||||
user_data["is_anonymous"] = True
|
||||
if original_user:
|
||||
original_data = get_user(original_user)
|
||||
original_data["on_behalf_of"] = user_data
|
||||
return original_data
|
||||
return user_data
|
||||
|
||||
|
||||
|
@ -4,10 +4,8 @@ from unittest.mock import MagicMock, PropertyMock, patch
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.test import override_settings
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
from rest_framework.exceptions import ParseError
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tests.utils import create_test_flow, create_test_user
|
||||
@ -650,25 +648,3 @@ class TestFlowExecutor(FlowTestCase):
|
||||
self.assertStageResponse(response, flow, component="ak-stage-identification")
|
||||
response = self.client.post(exec_url, {"uid_field": user_other.username}, follow=True)
|
||||
self.assertStageResponse(response, flow, component="ak-stage-access-denied")
|
||||
|
||||
@patch(
|
||||
"authentik.flows.views.executor.to_stage_response",
|
||||
TO_STAGE_RESPONSE_MOCK,
|
||||
)
|
||||
def test_invalid_json(self):
|
||||
"""Test invalid JSON body"""
|
||||
flow = create_test_flow()
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
)
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
|
||||
|
||||
with override_settings(TEST=False, DEBUG=False):
|
||||
self.client.logout()
|
||||
response = self.client.post(url, data="{", content_type="application/json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
with self.assertRaises(ParseError):
|
||||
self.client.logout()
|
||||
response = self.client.post(url, data="{", content_type="application/json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -55,8 +55,7 @@ from authentik.flows.planner import (
|
||||
FlowPlanner,
|
||||
)
|
||||
from authentik.flows.stage import AccessDeniedStage, StageView
|
||||
from authentik.lib.sentry import SentryIgnoredException, should_ignore_exception
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
||||
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
@ -234,13 +233,12 @@ class FlowExecutorView(APIView):
|
||||
"""Handle exception in stage execution"""
|
||||
if settings.DEBUG or settings.TEST:
|
||||
raise exc
|
||||
capture_exception(exc)
|
||||
self._logger.warning(exc)
|
||||
if not should_ignore_exception(exc):
|
||||
capture_exception(exc)
|
||||
Event.new(
|
||||
action=EventAction.SYSTEM_EXCEPTION,
|
||||
message=exception_to_string(exc),
|
||||
).from_http(self.request)
|
||||
Event.new(
|
||||
action=EventAction.SYSTEM_EXCEPTION,
|
||||
message="System exception during flow execution.",
|
||||
).with_exception(exc).from_http(self.request)
|
||||
challenge = FlowErrorChallenge(self.request, exc)
|
||||
challenge.is_valid(raise_exception=True)
|
||||
return to_stage_response(self.request, HttpChallengeResponse(challenge))
|
||||
|
@ -14,7 +14,6 @@ from django_redis.exceptions import ConnectionInterrupted
|
||||
from docker.errors import DockerException
|
||||
from h11 import LocalProtocolError
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
from psycopg.errors import Error
|
||||
from redis.exceptions import ConnectionError as RedisConnectionError
|
||||
from redis.exceptions import RedisError, ResponseError
|
||||
from rest_framework.exceptions import APIException
|
||||
@ -45,49 +44,6 @@ class SentryIgnoredException(Exception):
|
||||
"""Base Class for all errors that are suppressed, and not sent to sentry."""
|
||||
|
||||
|
||||
ignored_classes = (
|
||||
# Inbuilt types
|
||||
KeyboardInterrupt,
|
||||
ConnectionResetError,
|
||||
OSError,
|
||||
PermissionError,
|
||||
# Django Errors
|
||||
Error,
|
||||
ImproperlyConfigured,
|
||||
DatabaseError,
|
||||
OperationalError,
|
||||
InternalError,
|
||||
ProgrammingError,
|
||||
SuspiciousOperation,
|
||||
ValidationError,
|
||||
# Redis errors
|
||||
RedisConnectionError,
|
||||
ConnectionInterrupted,
|
||||
RedisError,
|
||||
ResponseError,
|
||||
# websocket errors
|
||||
ChannelFull,
|
||||
WebSocketException,
|
||||
LocalProtocolError,
|
||||
# rest_framework error
|
||||
APIException,
|
||||
# celery errors
|
||||
WorkerLostError,
|
||||
CeleryError,
|
||||
SoftTimeLimitExceeded,
|
||||
# custom baseclass
|
||||
SentryIgnoredException,
|
||||
# ldap errors
|
||||
LDAPException,
|
||||
# Docker errors
|
||||
DockerException,
|
||||
# End-user errors
|
||||
Http404,
|
||||
# AsyncIO
|
||||
CancelledError,
|
||||
)
|
||||
|
||||
|
||||
class SentryTransport(HttpTransport):
|
||||
"""Custom sentry transport with custom user-agent"""
|
||||
|
||||
@ -145,17 +101,56 @@ def traces_sampler(sampling_context: dict) -> float:
|
||||
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
||||
|
||||
|
||||
def should_ignore_exception(exc: Exception) -> bool:
|
||||
"""Check if an exception should be dropped"""
|
||||
return isinstance(exc, ignored_classes)
|
||||
|
||||
|
||||
def before_send(event: dict, hint: dict) -> dict | None:
|
||||
"""Check if error is database error, and ignore if so"""
|
||||
|
||||
from psycopg.errors import Error
|
||||
|
||||
ignored_classes = (
|
||||
# Inbuilt types
|
||||
KeyboardInterrupt,
|
||||
ConnectionResetError,
|
||||
OSError,
|
||||
PermissionError,
|
||||
# Django Errors
|
||||
Error,
|
||||
ImproperlyConfigured,
|
||||
DatabaseError,
|
||||
OperationalError,
|
||||
InternalError,
|
||||
ProgrammingError,
|
||||
SuspiciousOperation,
|
||||
ValidationError,
|
||||
# Redis errors
|
||||
RedisConnectionError,
|
||||
ConnectionInterrupted,
|
||||
RedisError,
|
||||
ResponseError,
|
||||
# websocket errors
|
||||
ChannelFull,
|
||||
WebSocketException,
|
||||
LocalProtocolError,
|
||||
# rest_framework error
|
||||
APIException,
|
||||
# celery errors
|
||||
WorkerLostError,
|
||||
CeleryError,
|
||||
SoftTimeLimitExceeded,
|
||||
# custom baseclass
|
||||
SentryIgnoredException,
|
||||
# ldap errors
|
||||
LDAPException,
|
||||
# Docker errors
|
||||
DockerException,
|
||||
# End-user errors
|
||||
Http404,
|
||||
# AsyncIO
|
||||
CancelledError,
|
||||
)
|
||||
exc_value = None
|
||||
if "exc_info" in hint:
|
||||
_, exc_value, _ = hint["exc_info"]
|
||||
if should_ignore_exception(exc_value):
|
||||
if isinstance(exc_value, ignored_classes):
|
||||
LOGGER.debug("dropping exception", exc=exc_value)
|
||||
return None
|
||||
if "logger" in event:
|
||||
|
@ -14,7 +14,6 @@ from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.expression.exceptions import ControlFlowException
|
||||
from authentik.lib.sync.mapper import PropertyMappingManager
|
||||
from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.db.models import Model
|
||||
@ -106,9 +105,9 @@ class BaseOutgoingSyncClient[
|
||||
# Value error can be raised when assigning invalid data to an attribute
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message=f"Failed to evaluate property-mapping {exception_to_string(exc)}",
|
||||
message="Failed to evaluate property-mapping",
|
||||
mapping=exc.mapping,
|
||||
).save()
|
||||
).with_exception(exc).save()
|
||||
raise StopSync(exc, obj, exc.mapping) from exc
|
||||
if not raw_final_object:
|
||||
raise StopSync(ValueError("No mappings configured"), obj)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.lib.sentry import SentryIgnoredException, should_ignore_exception
|
||||
from authentik.lib.sentry import SentryIgnoredException, before_send
|
||||
|
||||
|
||||
class TestSentry(TestCase):
|
||||
@ -10,8 +10,8 @@ class TestSentry(TestCase):
|
||||
|
||||
def test_error_not_sent(self):
|
||||
"""Test SentryIgnoredError not sent"""
|
||||
self.assertTrue(should_ignore_exception(SentryIgnoredException()))
|
||||
self.assertIsNone(before_send({}, {"exc_info": (0, SentryIgnoredException(), 0)}))
|
||||
|
||||
def test_error_sent(self):
|
||||
"""Test error sent"""
|
||||
self.assertFalse(should_ignore_exception(ValueError()))
|
||||
self.assertEqual({}, before_send({}, {"exc_info": (0, ValueError(), 0)}))
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
from traceback import extract_tb
|
||||
|
||||
from structlog.tracebacks import ExceptionDictTransformer
|
||||
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
|
||||
TRACEBACK_HEADER = "Traceback (most recent call last):"
|
||||
@ -17,3 +19,8 @@ def exception_to_string(exc: Exception) -> str:
|
||||
f"{class_to_path(exc.__class__)}: {str(exc)}",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def exception_to_dict(exc: Exception) -> dict:
|
||||
"""Format exception as a dictionary"""
|
||||
return ExceptionDictTransformer()((type(exc), exc, exc.__traceback__))
|
||||
|
@ -35,7 +35,6 @@ from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import InheritanceForeignKey, SerializerModel
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.outposts.controllers.k8s.utils import get_namespace
|
||||
|
||||
OUR_VERSION = parse(__version__)
|
||||
@ -326,9 +325,8 @@ class Outpost(SerializerModel, ManagedModel):
|
||||
"While setting the permissions for the service-account, a "
|
||||
"permission was not found: Check "
|
||||
"https://goauthentik.io/docs/troubleshooting/missing_permission"
|
||||
)
|
||||
+ exception_to_string(exc),
|
||||
).set_user(user).save()
|
||||
),
|
||||
).with_exception(exc).set_user(user).save()
|
||||
else:
|
||||
app_label, perm = model_or_perm.split(".")
|
||||
permission = Permission.objects.filter(
|
||||
|
@ -1,13 +1,15 @@
|
||||
"""authentik outpost signals"""
|
||||
|
||||
from django.contrib.auth.signals import user_logged_out
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import m2m_changed, post_save, pre_delete, pre_save
|
||||
from django.dispatch import receiver
|
||||
from django.http import HttpRequest
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.models import AuthenticatedSession, Provider
|
||||
from authentik.core.models import AuthenticatedSession, Provider, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
from authentik.outposts.models import Outpost, OutpostServiceConnection
|
||||
@ -80,6 +82,14 @@ def pre_delete_cleanup(sender, instance: Outpost, **_):
|
||||
outpost_controller.delay(instance.pk.hex, action="down", from_cache=True)
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def logout_revoke_direct(sender: type[User], request: HttpRequest, **_):
|
||||
"""Catch logout by direct logout and forward to providers"""
|
||||
if not request.session or not request.session.session_key:
|
||||
return
|
||||
outpost_session_end.delay(request.session.session_key)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
def logout_revoke(sender: type[AuthenticatedSession], instance: AuthenticatedSession, **_):
|
||||
"""Catch logout by expiring sessions being deleted"""
|
||||
|
@ -10,7 +10,7 @@ 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_string
|
||||
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
|
||||
@ -95,10 +95,13 @@ class PolicyProcess(PROCESS_CLASS):
|
||||
except PolicyException as exc:
|
||||
# Either use passed original exception or whatever we have
|
||||
src_exc = exc.src_exc if exc.src_exc else exc
|
||||
error_string = exception_to_string(src_exc)
|
||||
# Create policy exception event, only when we're not debugging
|
||||
if not self.request.debug:
|
||||
self.create_event(EventAction.POLICY_EXCEPTION, message=error_string)
|
||||
self.create_event(
|
||||
EventAction.POLICY_EXCEPTION,
|
||||
message="Policy failed to execute",
|
||||
exception=exception_to_dict(src_exc),
|
||||
)
|
||||
LOGGER.debug("P_ENG(proc): error, using failure result", exc=src_exc)
|
||||
policy_result = PolicyResult(self.binding.failure_result, str(src_exc))
|
||||
policy_result.source_binding = self.binding
|
||||
@ -143,5 +146,5 @@ class PolicyProcess(PROCESS_CLASS):
|
||||
try:
|
||||
self.connection.send(self.profiling_wrapper())
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Policy failed to run", exc=exception_to_string(exc))
|
||||
LOGGER.warning("Policy failed to run", exc=exc)
|
||||
self.connection.send(PolicyResult(False, str(exc)))
|
||||
|
@ -237,4 +237,4 @@ class TestPolicyProcess(TestCase):
|
||||
self.assertEqual(len(events), 1)
|
||||
event = events.first()
|
||||
self.assertEqual(event.user["username"], self.user.username)
|
||||
self.assertIn("division by zero", event.context["message"])
|
||||
self.assertIn("Policy failed to execute", event.context["message"])
|
||||
|
@ -1,10 +1,23 @@
|
||||
from django.contrib.auth.signals import user_logged_out
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.http import HttpRequest
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, User
|
||||
from authentik.providers.oauth2.models import AccessToken, DeviceToken, RefreshToken
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def user_logged_out_oauth_tokens_removal(sender, request: HttpRequest, user: User, **_):
|
||||
"""Revoke tokens upon user logout"""
|
||||
if not request.session or not request.session.session_key:
|
||||
return
|
||||
AccessToken.objects.filter(
|
||||
user=user,
|
||||
session__session__session_key=request.session.session_key,
|
||||
).delete()
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
def user_session_deleted_oauth_tokens_removal(sender, instance: AuthenticatedSession, **_):
|
||||
"""Revoke tokens upon user logout"""
|
||||
|
@ -2,11 +2,13 @@
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django.contrib.auth.signals import user_logged_out
|
||||
from django.core.cache import cache
|
||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.http import HttpRequest
|
||||
|
||||
from authentik.core.models import AuthenticatedSession
|
||||
from authentik.core.models import AuthenticatedSession, User
|
||||
from authentik.providers.rac.api.endpoints import user_endpoint_cache_key
|
||||
from authentik.providers.rac.consumer_client import (
|
||||
RAC_CLIENT_GROUP_SESSION,
|
||||
@ -15,6 +17,21 @@ from authentik.providers.rac.consumer_client import (
|
||||
from authentik.providers.rac.models import ConnectionToken, Endpoint
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
|
||||
"""Disconnect any open RAC connections"""
|
||||
if not request.session or not request.session.session_key:
|
||||
return
|
||||
layer = get_channel_layer()
|
||||
async_to_sync(layer.group_send)(
|
||||
RAC_CLIENT_GROUP_SESSION
|
||||
% {
|
||||
"session": request.session.session_key,
|
||||
},
|
||||
{"type": "event.disconnect", "reason": "session_logout"},
|
||||
)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
def user_session_deleted(sender, instance: AuthenticatedSession, **_):
|
||||
layer = get_channel_layer()
|
||||
|
@ -23,7 +23,6 @@ from authentik.core.models import Application
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.expression.exceptions import ControlFlowException
|
||||
from authentik.lib.sync.mapper import PropertyMappingManager
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.policies.api.exec import PolicyTestResultSerializer
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
from authentik.policies.types import PolicyResult
|
||||
@ -142,9 +141,9 @@ class RadiusOutpostConfigViewSet(ListModelMixin, GenericViewSet):
|
||||
# Value error can be raised when assigning invalid data to an attribute
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message=f"Failed to evaluate property-mapping {exception_to_string(exc)}",
|
||||
message="Failed to evaluate property-mapping",
|
||||
mapping=exc.mapping,
|
||||
).save()
|
||||
).with_exception(exc).save()
|
||||
return None
|
||||
return b64encode(packet.RequestPacket()).decode()
|
||||
|
||||
|
@ -5,6 +5,7 @@ from itertools import batched
|
||||
from django.db import transaction
|
||||
from pydantic import ValidationError
|
||||
from pydanticscim.group import GroupMember
|
||||
from pydanticscim.responses import PatchOp
|
||||
|
||||
from authentik.core.models import Group
|
||||
from authentik.lib.sync.mapper import PropertyMappingManager
|
||||
@ -19,12 +20,7 @@ from authentik.providers.scim.clients.base import SCIMClient
|
||||
from authentik.providers.scim.clients.exceptions import (
|
||||
SCIMRequestException,
|
||||
)
|
||||
from authentik.providers.scim.clients.schema import (
|
||||
SCIM_GROUP_SCHEMA,
|
||||
PatchOp,
|
||||
PatchOperation,
|
||||
PatchRequest,
|
||||
)
|
||||
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchOperation, PatchRequest
|
||||
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
|
||||
from authentik.providers.scim.models import (
|
||||
SCIMMapping,
|
||||
|
@ -1,7 +1,5 @@
|
||||
"""Custom SCIM schemas"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import Field
|
||||
from pydanticscim.group import Group as BaseGroup
|
||||
from pydanticscim.responses import PatchOperation as BasePatchOperation
|
||||
@ -67,21 +65,6 @@ class ServiceProviderConfiguration(BaseServiceProviderConfiguration):
|
||||
)
|
||||
|
||||
|
||||
class PatchOp(str, Enum):
|
||||
|
||||
replace = "replace"
|
||||
remove = "remove"
|
||||
add = "add"
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
value = value.lower()
|
||||
for member in cls:
|
||||
if member.lower() == value:
|
||||
return member
|
||||
return None
|
||||
|
||||
|
||||
class PatchRequest(BasePatchRequest):
|
||||
"""PatchRequest which correctly sets schemas"""
|
||||
|
||||
@ -91,7 +74,6 @@ class PatchRequest(BasePatchRequest):
|
||||
class PatchOperation(BasePatchOperation):
|
||||
"""PatchOperation with optional path"""
|
||||
|
||||
op: PatchOp
|
||||
path: str | None
|
||||
|
||||
|
||||
|
@ -27,8 +27,7 @@ from structlog.stdlib import get_logger
|
||||
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
|
||||
|
||||
from authentik import get_full_version
|
||||
from authentik.lib.sentry import should_ignore_exception
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.sentry import before_send
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
||||
@ -81,10 +80,10 @@ def task_error_hook(task_id: str, exception: Exception, traceback, *args, **kwar
|
||||
|
||||
LOGGER.warning("Task failure", task_id=task_id.replace("-", ""), exc=exception)
|
||||
CTX_TASK_ID.set(...)
|
||||
if not should_ignore_exception(exception):
|
||||
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
||||
Event.new(
|
||||
EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception), task_id=task_id
|
||||
).save()
|
||||
EventAction.SYSTEM_EXCEPTION, message="Failed to execute task", task_id=task_id
|
||||
).with_exception(exception).save()
|
||||
|
||||
|
||||
def _get_startup_tasks_default_tenant() -> list[Callable]:
|
||||
|
@ -1,49 +1,13 @@
|
||||
"""authentik database backend"""
|
||||
|
||||
from django.core.checks import Warning
|
||||
from django.db.backends.base.validation import BaseDatabaseValidation
|
||||
from django_tenants.postgresql_backend.base import DatabaseWrapper as BaseDatabaseWrapper
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
class DatabaseValidation(BaseDatabaseValidation):
|
||||
|
||||
def check(self, **kwargs):
|
||||
return self._check_encoding()
|
||||
|
||||
def _check_encoding(self):
|
||||
"""Throw a warning when the server_encoding is not UTF-8 or
|
||||
server_encoding and client_encoding are mismatched"""
|
||||
messages = []
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute("SHOW server_encoding;")
|
||||
server_encoding = cursor.fetchone()[0]
|
||||
cursor.execute("SHOW client_encoding;")
|
||||
client_encoding = cursor.fetchone()[0]
|
||||
if server_encoding != client_encoding:
|
||||
messages.append(
|
||||
Warning(
|
||||
"PostgreSQL Server and Client encoding are mismatched: Server: "
|
||||
f"{server_encoding}, Client: {client_encoding}",
|
||||
id="ak.db.W001",
|
||||
)
|
||||
)
|
||||
if server_encoding != "UTF8":
|
||||
messages.append(
|
||||
Warning(
|
||||
f"PostgreSQL Server encoding is not UTF8: {server_encoding}",
|
||||
id="ak.db.W002",
|
||||
)
|
||||
)
|
||||
return messages
|
||||
|
||||
|
||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
"""database backend which supports rotating credentials"""
|
||||
|
||||
validation_class = DatabaseValidation
|
||||
|
||||
def get_connection_params(self):
|
||||
"""Refresh DB credentials before getting connection params"""
|
||||
conn_params = super().get_connection_params()
|
||||
|
@ -8,7 +8,6 @@ from authentik.events.models import TaskStatus
|
||||
from authentik.events.system_tasks import SystemTask
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.sync.outgoing.exceptions import StopSync
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.root.celery import CELERY_APP
|
||||
from authentik.sources.kerberos.models import KerberosSource
|
||||
from authentik.sources.kerberos.sync import KerberosSync
|
||||
@ -64,5 +63,5 @@ def kerberos_sync_single(self, source_pk: str):
|
||||
syncer.sync()
|
||||
self.set_status(TaskStatus.SUCCESSFUL, *syncer.messages)
|
||||
except StopSync as exc:
|
||||
LOGGER.warning(exception_to_string(exc))
|
||||
LOGGER.warning("Error syncing kerberos", exc=exc, source=source)
|
||||
self.set_error(exc)
|
||||
|
@ -12,7 +12,6 @@ from authentik.events.models import TaskStatus
|
||||
from authentik.events.system_tasks import SystemTask
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.sync.outgoing.exceptions import StopSync
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.reflection import class_to_path, path_to_class
|
||||
from authentik.root.celery import CELERY_APP
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
@ -149,5 +148,5 @@ def ldap_sync(self: SystemTask, source_pk: str, sync_class: str, page_cache_key:
|
||||
cache.delete(page_cache_key)
|
||||
except (LDAPException, StopSync) as exc:
|
||||
# No explicit event is created here as .set_status with an error will do that
|
||||
LOGGER.warning(exception_to_string(exc))
|
||||
LOGGER.warning("Failed to sync LDAP", exc=exc, source=source)
|
||||
self.set_error(exc)
|
||||
|
@ -1,277 +0,0 @@
|
||||
"""Test SCIM Group"""
|
||||
|
||||
from json import dumps
|
||||
from uuid import uuid4
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Group
|
||||
from authentik.core.tests.utils import create_test_user
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
|
||||
from authentik.sources.scim.models import (
|
||||
SCIMSource,
|
||||
SCIMSourceGroup,
|
||||
)
|
||||
from authentik.sources.scim.views.v2.base import SCIM_CONTENT_TYPE
|
||||
|
||||
|
||||
class TestSCIMGroups(APITestCase):
|
||||
"""Test SCIM Group view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
|
||||
|
||||
def test_group_list(self):
|
||||
"""Test full group list"""
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_group_list_single(self):
|
||||
"""Test full group list (single group)"""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
user = create_test_user()
|
||||
group.users.add(user)
|
||||
SCIMSourceGroup.objects.create(
|
||||
source=self.source,
|
||||
group=group,
|
||||
id=str(uuid4()),
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
"group_id": str(group.pk),
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, second=200)
|
||||
SCIMGroupSchema.model_validate_json(response.content, strict=True)
|
||||
|
||||
def test_group_create(self):
|
||||
"""Test group create"""
|
||||
ext_id = generate_id()
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
data=dumps({"displayName": generate_id(), "externalId": ext_id}),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
|
||||
self.assertTrue(
|
||||
Event.objects.filter(
|
||||
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||
).exists()
|
||||
)
|
||||
|
||||
def test_group_create_members(self):
|
||||
"""Test group create"""
|
||||
user = create_test_user()
|
||||
ext_id = generate_id()
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
data=dumps(
|
||||
{
|
||||
"displayName": generate_id(),
|
||||
"externalId": ext_id,
|
||||
"members": [{"value": str(user.uuid)}],
|
||||
}
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
|
||||
self.assertTrue(
|
||||
Event.objects.filter(
|
||||
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||
).exists()
|
||||
)
|
||||
|
||||
def test_group_create_members_empty(self):
|
||||
"""Test group create"""
|
||||
ext_id = generate_id()
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
data=dumps({"displayName": generate_id(), "externalId": ext_id, "members": []}),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
|
||||
self.assertTrue(
|
||||
Event.objects.filter(
|
||||
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||
).exists()
|
||||
)
|
||||
|
||||
def test_group_create_duplicate(self):
|
||||
"""Test group create (duplicate)"""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
existing = SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||
ext_id = generate_id()
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
},
|
||||
),
|
||||
data=dumps(
|
||||
{"displayName": generate_id(), "externalId": ext_id, "id": str(existing.group.pk)}
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 409)
|
||||
self.assertJSONEqual(
|
||||
response.content,
|
||||
{
|
||||
"detail": "Group with ID exists already.",
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"scimType": "uniqueness",
|
||||
"status": 409,
|
||||
},
|
||||
)
|
||||
|
||||
def test_group_update(self):
|
||||
"""Test group update"""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
existing = SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||
ext_id = generate_id()
|
||||
response = self.client.put(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||
),
|
||||
data=dumps(
|
||||
{"displayName": generate_id(), "externalId": ext_id, "id": str(existing.pk)}
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, second=200)
|
||||
|
||||
def test_group_update_non_existent(self):
|
||||
"""Test group update"""
|
||||
ext_id = generate_id()
|
||||
response = self.client.put(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
"group_id": str(uuid4()),
|
||||
},
|
||||
),
|
||||
data=dumps({"displayName": generate_id(), "externalId": ext_id, "id": ""}),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, second=404)
|
||||
self.assertJSONEqual(
|
||||
response.content,
|
||||
{
|
||||
"detail": "Group not found.",
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status": 404,
|
||||
},
|
||||
)
|
||||
|
||||
def test_group_patch_add(self):
|
||||
"""Test group patch"""
|
||||
user = create_test_user()
|
||||
|
||||
group = Group.objects.create(name=generate_id())
|
||||
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||
response = self.client.patch(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||
),
|
||||
data=dumps(
|
||||
{
|
||||
"Operations": [
|
||||
{
|
||||
"op": "Add",
|
||||
"path": "members",
|
||||
"value": {"value": str(user.uuid)},
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, second=200)
|
||||
self.assertTrue(group.users.filter(pk=user.pk).exists())
|
||||
|
||||
def test_group_patch_remove(self):
|
||||
"""Test group patch"""
|
||||
user = create_test_user()
|
||||
|
||||
group = Group.objects.create(name=generate_id())
|
||||
group.users.add(user)
|
||||
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||
response = self.client.patch(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||
),
|
||||
data=dumps(
|
||||
{
|
||||
"Operations": [
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "members",
|
||||
"value": {"value": str(user.uuid)},
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, second=200)
|
||||
self.assertFalse(group.users.filter(pk=user.pk).exists())
|
||||
|
||||
def test_group_delete(self):
|
||||
"""Test group delete"""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||
response = self.client.delete(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-groups",
|
||||
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, second=204)
|
@ -177,51 +177,3 @@ class TestSCIMUsers(APITestCase):
|
||||
SCIMSourceUser.objects.get(source=self.source, id=ext_id).user.attributes["phone"],
|
||||
"0123456789",
|
||||
)
|
||||
|
||||
def test_user_update(self):
|
||||
"""Test user update"""
|
||||
user = create_test_user()
|
||||
existing = SCIMSourceUser.objects.create(source=self.source, user=user, id=uuid4())
|
||||
ext_id = generate_id()
|
||||
response = self.client.put(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-users",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
"user_id": str(user.uuid),
|
||||
},
|
||||
),
|
||||
data=dumps(
|
||||
{
|
||||
"id": str(existing.pk),
|
||||
"userName": generate_id(),
|
||||
"externalId": ext_id,
|
||||
"emails": [
|
||||
{
|
||||
"primary": True,
|
||||
"value": user.email,
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_user_delete(self):
|
||||
"""Test user delete"""
|
||||
user = create_test_user()
|
||||
SCIMSourceUser.objects.create(source=self.source, user=user, id=uuid4())
|
||||
response = self.client.delete(
|
||||
reverse(
|
||||
"authentik_sources_scim:v2-users",
|
||||
kwargs={
|
||||
"source_slug": self.source.slug,
|
||||
"user_id": str(user.uuid),
|
||||
},
|
||||
),
|
||||
content_type=SCIM_CONTENT_TYPE,
|
||||
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
|
@ -8,7 +8,6 @@ from rest_framework.authentication import BaseAuthentication, get_authorization_
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from authentik.core.middleware import CTX_AUTH_VIA
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
@ -27,7 +26,6 @@ class SCIMTokenAuth(BaseAuthentication):
|
||||
_username, _, password = b64decode(key.encode()).decode().partition(":")
|
||||
token = self.check_token(password, source_slug)
|
||||
if token:
|
||||
CTX_AUTH_VIA.set("scim_basic")
|
||||
return (token.user, token)
|
||||
return None
|
||||
|
||||
@ -54,5 +52,4 @@ class SCIMTokenAuth(BaseAuthentication):
|
||||
token = self.check_token(key, source_slug)
|
||||
if not token:
|
||||
return None
|
||||
CTX_AUTH_VIA.set("scim_token")
|
||||
return (token.user, token)
|
||||
|
@ -1,11 +1,13 @@
|
||||
"""SCIM Utils"""
|
||||
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.paginator import Page, Paginator
|
||||
from django.db.models import Q, QuerySet
|
||||
from django.http import HttpRequest
|
||||
from django.urls import resolve
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
@ -44,7 +46,7 @@ class SCIMView(APIView):
|
||||
logger: BoundLogger
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
parser_classes = [SCIMParser, JSONParser]
|
||||
parser_classes = [SCIMParser]
|
||||
renderer_classes = [SCIMRenderer]
|
||||
|
||||
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
|
||||
@ -54,6 +56,28 @@ class SCIMView(APIView):
|
||||
def get_authenticators(self):
|
||||
return [SCIMTokenAuth(self)]
|
||||
|
||||
def patch_resolve_value(self, raw_value: dict) -> User | Group | None:
|
||||
"""Attempt to resolve a raw `value` attribute of a patch operation into
|
||||
a database model"""
|
||||
model = User
|
||||
query = {}
|
||||
if "$ref" in raw_value:
|
||||
url = urlparse(raw_value["$ref"])
|
||||
if match := resolve(url.path):
|
||||
if match.url_name == "v2-users":
|
||||
model = User
|
||||
query = {"pk": int(match.kwargs["user_id"])}
|
||||
elif "type" in raw_value:
|
||||
match raw_value["type"]:
|
||||
case "User":
|
||||
model = User
|
||||
query = {"pk": int(raw_value["value"])}
|
||||
case "Group":
|
||||
model = Group
|
||||
else:
|
||||
return None
|
||||
return model.objects.filter(**query).first()
|
||||
|
||||
def filter_parse(self, request: Request):
|
||||
"""Parse the path of a Patch Operation"""
|
||||
path = request.query_params.get("filter")
|
||||
|
@ -1,58 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
from pydanticscim.responses import SCIMError as BaseSCIMError
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
|
||||
class SCIMErrorTypes(Enum):
|
||||
invalid_filter = "invalidFilter"
|
||||
too_many = "tooMany"
|
||||
uniqueness = "uniqueness"
|
||||
mutability = "mutability"
|
||||
invalid_syntax = "invalidSyntax"
|
||||
invalid_path = "invalidPath"
|
||||
no_target = "noTarget"
|
||||
invalid_value = "invalidValue"
|
||||
invalid_vers = "invalidVers"
|
||||
sensitive = "sensitive"
|
||||
|
||||
|
||||
class SCIMError(BaseSCIMError):
|
||||
scimType: SCIMErrorTypes | None = None
|
||||
detail: str | None = None
|
||||
|
||||
|
||||
class SCIMValidationError(ValidationError):
|
||||
status_code = 400
|
||||
default_detail = SCIMError(scimType=SCIMErrorTypes.invalid_syntax, status=400)
|
||||
|
||||
def __init__(self, detail: SCIMError | None):
|
||||
if detail is None:
|
||||
detail = self.default_detail
|
||||
detail.status = self.status_code
|
||||
self.detail = detail.model_dump(mode="json", exclude_none=True)
|
||||
|
||||
|
||||
class SCIMConflictError(SCIMValidationError):
|
||||
status_code = 409
|
||||
|
||||
def __init__(self, detail: str):
|
||||
super().__init__(
|
||||
SCIMError(
|
||||
detail=detail,
|
||||
scimType=SCIMErrorTypes.uniqueness,
|
||||
status=self.status_code,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class SCIMNotFoundError(SCIMValidationError):
|
||||
status_code = 404
|
||||
|
||||
def __init__(self, detail: str):
|
||||
super().__init__(
|
||||
SCIMError(
|
||||
detail=detail,
|
||||
status=self.status_code,
|
||||
)
|
||||
)
|
@ -4,25 +4,19 @@ from uuid import uuid4
|
||||
|
||||
from django.db.models import Q
|
||||
from django.db.transaction import atomic
|
||||
from django.http import QueryDict
|
||||
from django.http import Http404, QueryDict
|
||||
from django.urls import reverse
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
from pydanticscim.group import GroupMember
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from scim2_filter_parser.attr_paths import AttrPath
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchOp, PatchOperation
|
||||
from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
|
||||
from authentik.providers.scim.clients.schema import Group as SCIMGroupModel
|
||||
from authentik.sources.scim.models import SCIMSourceGroup
|
||||
from authentik.sources.scim.views.v2.base import SCIMObjectView
|
||||
from authentik.sources.scim.views.v2.exceptions import (
|
||||
SCIMConflictError,
|
||||
SCIMNotFoundError,
|
||||
SCIMValidationError,
|
||||
)
|
||||
|
||||
|
||||
class GroupsView(SCIMObjectView):
|
||||
@ -33,7 +27,7 @@ class GroupsView(SCIMObjectView):
|
||||
def group_to_scim(self, scim_group: SCIMSourceGroup) -> dict:
|
||||
"""Convert Group to SCIM data"""
|
||||
payload = SCIMGroupModel(
|
||||
schemas=[SCIM_GROUP_SCHEMA],
|
||||
schemas=[SCIM_USER_SCHEMA],
|
||||
id=str(scim_group.group.pk),
|
||||
externalId=scim_group.id,
|
||||
displayName=scim_group.group.name,
|
||||
@ -64,7 +58,7 @@ class GroupsView(SCIMObjectView):
|
||||
if group_id:
|
||||
connection = base_query.filter(source=self.source, group__group_uuid=group_id).first()
|
||||
if not connection:
|
||||
raise SCIMNotFoundError("Group not found.")
|
||||
raise Http404
|
||||
return Response(self.group_to_scim(connection))
|
||||
connections = (
|
||||
base_query.filter(source=self.source).order_by("pk").filter(self.filter_parse(request))
|
||||
@ -125,7 +119,7 @@ class GroupsView(SCIMObjectView):
|
||||
).first()
|
||||
if connection:
|
||||
self.logger.debug("Found existing group")
|
||||
raise SCIMConflictError("Group with ID exists already.")
|
||||
return Response(status=409)
|
||||
connection = self.update_group(None, request.data)
|
||||
return Response(self.group_to_scim(connection), status=201)
|
||||
|
||||
@ -135,44 +129,10 @@ class GroupsView(SCIMObjectView):
|
||||
source=self.source, group__group_uuid=group_id
|
||||
).first()
|
||||
if not connection:
|
||||
raise SCIMNotFoundError("Group not found.")
|
||||
raise Http404
|
||||
connection = self.update_group(connection, request.data)
|
||||
return Response(self.group_to_scim(connection), status=200)
|
||||
|
||||
@atomic
|
||||
def patch(self, request: Request, group_id: str, **kwargs) -> Response:
|
||||
"""Patch group handler"""
|
||||
connection = SCIMSourceGroup.objects.filter(
|
||||
source=self.source, group__group_uuid=group_id
|
||||
).first()
|
||||
if not connection:
|
||||
raise SCIMNotFoundError("Group not found.")
|
||||
|
||||
for _op in request.data.get("Operations", []):
|
||||
operation = PatchOperation.model_validate(_op)
|
||||
if operation.op.lower() not in ["add", "remove", "replace"]:
|
||||
raise SCIMValidationError()
|
||||
attr_path = AttrPath(f'{operation.path} eq ""', {})
|
||||
if attr_path.first_path == ("members", None, None):
|
||||
# FIXME: this can probably be de-duplicated
|
||||
if operation.op == PatchOp.add:
|
||||
if not isinstance(operation.value, list):
|
||||
operation.value = [operation.value]
|
||||
query = Q()
|
||||
for member in operation.value:
|
||||
query |= Q(uuid=member["value"])
|
||||
if query:
|
||||
connection.group.users.add(*User.objects.filter(query))
|
||||
elif operation.op == PatchOp.remove:
|
||||
if not isinstance(operation.value, list):
|
||||
operation.value = [operation.value]
|
||||
query = Q()
|
||||
for member in operation.value:
|
||||
query |= Q(uuid=member["value"])
|
||||
if query:
|
||||
connection.group.users.remove(*User.objects.filter(query))
|
||||
return Response(self.group_to_scim(connection), status=200)
|
||||
|
||||
@atomic
|
||||
def delete(self, request: Request, group_id: str, **kwargs) -> Response:
|
||||
"""Delete group handler"""
|
||||
@ -180,7 +140,7 @@ class GroupsView(SCIMObjectView):
|
||||
source=self.source, group__group_uuid=group_id
|
||||
).first()
|
||||
if not connection:
|
||||
raise SCIMNotFoundError("Group not found.")
|
||||
raise Http404
|
||||
connection.group.delete()
|
||||
connection.delete()
|
||||
return Response(status=204)
|
||||
|
@ -1,11 +1,11 @@
|
||||
"""SCIM Meta views"""
|
||||
|
||||
from django.http import Http404
|
||||
from django.urls import reverse
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from authentik.sources.scim.views.v2.base import SCIMView
|
||||
from authentik.sources.scim.views.v2.exceptions import SCIMNotFoundError
|
||||
|
||||
|
||||
class ResourceTypesView(SCIMView):
|
||||
@ -138,7 +138,7 @@ class ResourceTypesView(SCIMView):
|
||||
resource = [x for x in resource_types if x.get("id") == resource_type]
|
||||
if resource:
|
||||
return Response(resource[0])
|
||||
raise SCIMNotFoundError("Resource not found.")
|
||||
raise Http404
|
||||
return Response(
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
|
@ -3,12 +3,12 @@
|
||||
from json import loads
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
from django.urls import reverse
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from authentik.sources.scim.views.v2.base import SCIMView
|
||||
from authentik.sources.scim.views.v2.exceptions import SCIMNotFoundError
|
||||
|
||||
with open(
|
||||
settings.BASE_DIR / "authentik" / "sources" / "scim" / "schemas" / "schema.json",
|
||||
@ -44,7 +44,7 @@ class SchemaView(SCIMView):
|
||||
schema = [x for x in schemas if x.get("id") == schema_uri]
|
||||
if schema:
|
||||
return Response(schema[0])
|
||||
raise SCIMNotFoundError("Schema not found.")
|
||||
raise Http404
|
||||
return Response(
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
|
@ -33,8 +33,6 @@ class ServiceProviderConfigView(SCIMView):
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
||||
"authenticationSchemes": auth_schemas,
|
||||
# We only support patch for groups currently, so don't broadly advertise it.
|
||||
# Implementations that require Group patch will use it regardless of this flag.
|
||||
"patch": {"supported": False},
|
||||
"bulk": {"supported": False, "maxOperations": 0, "maxPayloadSize": 0},
|
||||
"filter": {
|
||||
|
@ -4,7 +4,7 @@ from uuid import uuid4
|
||||
|
||||
from django.db.models import Q
|
||||
from django.db.transaction import atomic
|
||||
from django.http import QueryDict
|
||||
from django.http import Http404, QueryDict
|
||||
from django.urls import reverse
|
||||
from pydanticscim.user import Email, EmailKind, Name
|
||||
from rest_framework.exceptions import ValidationError
|
||||
@ -16,7 +16,6 @@ from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
|
||||
from authentik.providers.scim.clients.schema import User as SCIMUserModel
|
||||
from authentik.sources.scim.models import SCIMSourceUser
|
||||
from authentik.sources.scim.views.v2.base import SCIMObjectView
|
||||
from authentik.sources.scim.views.v2.exceptions import SCIMConflictError, SCIMNotFoundError
|
||||
|
||||
|
||||
class UsersView(SCIMObjectView):
|
||||
@ -70,7 +69,7 @@ class UsersView(SCIMObjectView):
|
||||
.first()
|
||||
)
|
||||
if not connection:
|
||||
raise SCIMNotFoundError("User not found.")
|
||||
raise Http404
|
||||
return Response(self.user_to_scim(connection))
|
||||
connections = (
|
||||
SCIMSourceUser.objects.filter(source=self.source).select_related("user").order_by("pk")
|
||||
@ -123,7 +122,7 @@ class UsersView(SCIMObjectView):
|
||||
).first()
|
||||
if connection:
|
||||
self.logger.debug("Found existing user")
|
||||
raise SCIMConflictError("Group with ID exists already.")
|
||||
return Response(status=409)
|
||||
connection = self.update_user(None, request.data)
|
||||
return Response(self.user_to_scim(connection), status=201)
|
||||
|
||||
@ -131,7 +130,7 @@ class UsersView(SCIMObjectView):
|
||||
"""Update user handler"""
|
||||
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()
|
||||
if not connection:
|
||||
raise SCIMNotFoundError("User not found.")
|
||||
raise Http404
|
||||
self.update_user(connection, request.data)
|
||||
return Response(self.user_to_scim(connection), status=200)
|
||||
|
||||
@ -140,7 +139,7 @@ class UsersView(SCIMObjectView):
|
||||
"""Delete user handler"""
|
||||
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()
|
||||
if not connection:
|
||||
raise SCIMNotFoundError("User not found.")
|
||||
raise Http404
|
||||
connection.user.delete()
|
||||
connection.delete()
|
||||
return Response(status=204)
|
||||
|
@ -13,7 +13,6 @@ from authentik.flows.exceptions import StageInvalidException
|
||||
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import SerializerModel
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
from authentik.stages.authenticator.models import SideChannelDevice
|
||||
from authentik.stages.email.utils import TemplateEmailMessage
|
||||
@ -160,9 +159,8 @@ class EmailDevice(SerializerModel, SideChannelDevice):
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message=_("Exception occurred while rendering E-mail template"),
|
||||
error=exception_to_string(exc),
|
||||
template=stage.template,
|
||||
).from_http(self.request)
|
||||
).with_exception(exc).from_http(self.request)
|
||||
raise StageInvalidException from exc
|
||||
|
||||
def __str__(self):
|
||||
|
@ -17,7 +17,6 @@ from authentik.flows.challenge import (
|
||||
from authentik.flows.exceptions import StageInvalidException
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
from authentik.lib.utils.email import mask_email
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.stages.authenticator_email.models import (
|
||||
AuthenticatorEmailStage,
|
||||
@ -100,9 +99,8 @@ class AuthenticatorEmailStageView(ChallengeStageView):
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message=_("Exception occurred while rendering E-mail template"),
|
||||
error=exception_to_string(exc),
|
||||
template=stage.template,
|
||||
).from_http(self.request)
|
||||
).with_exception(exc).from_http(self.request)
|
||||
raise StageInvalidException from exc
|
||||
|
||||
def _has_email(self) -> str | None:
|
||||
|
@ -19,7 +19,6 @@ from authentik.events.models import Event, EventAction, NotificationWebhookMappi
|
||||
from authentik.events.utils import sanitize_item
|
||||
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
|
||||
from authentik.lib.models import SerializerModel
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.stages.authenticator.models import SideChannelDevice
|
||||
|
||||
@ -142,10 +141,9 @@ class AuthenticatorSMSStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message="Error sending SMS",
|
||||
exc=exception_to_string(exc),
|
||||
status_code=response.status_code,
|
||||
body=response.text,
|
||||
).set_user(device.user).save()
|
||||
).with_exception(exc).set_user(device.user).save()
|
||||
if response.status_code >= HttpResponseBadRequest.status_code:
|
||||
raise ValidationError(response.text) from None
|
||||
raise
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Validation stage challenge checking"""
|
||||
|
||||
from json import loads
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.http import HttpRequest
|
||||
@ -37,12 +36,10 @@ from authentik.stages.authenticator_email.models import EmailDevice
|
||||
from authentik.stages.authenticator_sms.models import SMSDevice
|
||||
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
|
||||
from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice
|
||||
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
|
||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
||||
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
||||
|
||||
LOGGER = get_logger()
|
||||
if TYPE_CHECKING:
|
||||
from authentik.stages.authenticator_validate.stage import AuthenticatorValidateStageView
|
||||
|
||||
|
||||
class DeviceChallenge(PassiveSerializer):
|
||||
@ -55,11 +52,11 @@ class DeviceChallenge(PassiveSerializer):
|
||||
|
||||
|
||||
def get_challenge_for_device(
|
||||
stage_view: "AuthenticatorValidateStageView", stage: AuthenticatorValidateStage, device: Device
|
||||
request: HttpRequest, stage: AuthenticatorValidateStage, device: Device
|
||||
) -> dict:
|
||||
"""Generate challenge for a single device"""
|
||||
if isinstance(device, WebAuthnDevice):
|
||||
return get_webauthn_challenge(stage_view, stage, device)
|
||||
return get_webauthn_challenge(request, stage, device)
|
||||
if isinstance(device, EmailDevice):
|
||||
return {"email": mask_email(device.email)}
|
||||
# Code-based challenges have no hints
|
||||
@ -67,30 +64,26 @@ def get_challenge_for_device(
|
||||
|
||||
|
||||
def get_webauthn_challenge_without_user(
|
||||
stage_view: "AuthenticatorValidateStageView", stage: AuthenticatorValidateStage
|
||||
request: HttpRequest, stage: AuthenticatorValidateStage
|
||||
) -> dict:
|
||||
"""Same as `get_webauthn_challenge`, but allows any client device. We can then later check
|
||||
who the device belongs to."""
|
||||
stage_view.executor.plan.context.pop(PLAN_CONTEXT_WEBAUTHN_CHALLENGE, None)
|
||||
request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
||||
authentication_options = generate_authentication_options(
|
||||
rp_id=get_rp_id(stage_view.request),
|
||||
rp_id=get_rp_id(request),
|
||||
allow_credentials=[],
|
||||
user_verification=UserVerificationRequirement(stage.webauthn_user_verification),
|
||||
)
|
||||
stage_view.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = (
|
||||
authentication_options.challenge
|
||||
)
|
||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = authentication_options.challenge
|
||||
|
||||
return loads(options_to_json(authentication_options))
|
||||
|
||||
|
||||
def get_webauthn_challenge(
|
||||
stage_view: "AuthenticatorValidateStageView",
|
||||
stage: AuthenticatorValidateStage,
|
||||
device: WebAuthnDevice | None = None,
|
||||
request: HttpRequest, stage: AuthenticatorValidateStage, device: WebAuthnDevice | None = None
|
||||
) -> dict:
|
||||
"""Send the client a challenge that we'll check later"""
|
||||
stage_view.executor.plan.context.pop(PLAN_CONTEXT_WEBAUTHN_CHALLENGE, None)
|
||||
request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
||||
|
||||
allowed_credentials = []
|
||||
|
||||
@ -101,14 +94,12 @@ def get_webauthn_challenge(
|
||||
allowed_credentials.append(user_device.descriptor)
|
||||
|
||||
authentication_options = generate_authentication_options(
|
||||
rp_id=get_rp_id(stage_view.request),
|
||||
rp_id=get_rp_id(request),
|
||||
allow_credentials=allowed_credentials,
|
||||
user_verification=UserVerificationRequirement(stage.webauthn_user_verification),
|
||||
)
|
||||
|
||||
stage_view.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = (
|
||||
authentication_options.challenge
|
||||
)
|
||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = authentication_options.challenge
|
||||
|
||||
return loads(options_to_json(authentication_options))
|
||||
|
||||
@ -155,7 +146,7 @@ def validate_challenge_code(code: str, stage_view: StageView, user: User) -> Dev
|
||||
def validate_challenge_webauthn(data: dict, stage_view: StageView, user: User) -> Device:
|
||||
"""Validate WebAuthn Challenge"""
|
||||
request = stage_view.request
|
||||
challenge = stage_view.executor.plan.context.get(PLAN_CONTEXT_WEBAUTHN_CHALLENGE)
|
||||
challenge = request.session.get(SESSION_KEY_WEBAUTHN_CHALLENGE)
|
||||
stage: AuthenticatorValidateStage = stage_view.executor.current_stage
|
||||
try:
|
||||
credential = parse_authentication_credential_json(data)
|
||||
|
@ -224,7 +224,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||
data={
|
||||
"device_class": device_class,
|
||||
"device_uid": device.pk,
|
||||
"challenge": get_challenge_for_device(self, stage, device),
|
||||
"challenge": get_challenge_for_device(self.request, stage, device),
|
||||
"last_used": device.last_used,
|
||||
}
|
||||
)
|
||||
@ -243,7 +243,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||
"device_class": DeviceClasses.WEBAUTHN,
|
||||
"device_uid": -1,
|
||||
"challenge": get_webauthn_challenge_without_user(
|
||||
self,
|
||||
self.request,
|
||||
self.executor.current_stage,
|
||||
),
|
||||
"last_used": None,
|
||||
|
@ -31,7 +31,7 @@ from authentik.stages.authenticator_webauthn.models import (
|
||||
WebAuthnDevice,
|
||||
WebAuthnDeviceType,
|
||||
)
|
||||
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
|
||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
||||
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
|
||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||
from authentik.stages.user_login.models import UserLoginStage
|
||||
@ -103,11 +103,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
device_classes=[DeviceClasses.WEBAUTHN],
|
||||
webauthn_user_verification=UserVerification.PREFERRED,
|
||||
)
|
||||
plan = FlowPlan("")
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
|
||||
)
|
||||
challenge = get_challenge_for_device(stage_view, stage, webauthn_device)
|
||||
challenge = get_challenge_for_device(request, stage, webauthn_device)
|
||||
del challenge["challenge"]
|
||||
self.assertEqual(
|
||||
challenge,
|
||||
@ -126,9 +122,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
validate_challenge_webauthn(
|
||||
{},
|
||||
StageView(FlowExecutorView(current_stage=stage, plan=plan), request=request),
|
||||
self.user,
|
||||
{}, StageView(FlowExecutorView(current_stage=stage), request=request), self.user
|
||||
)
|
||||
|
||||
def test_device_challenge_webauthn_restricted(self):
|
||||
@ -199,35 +193,22 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
sign_count=0,
|
||||
rp_id=generate_id(),
|
||||
)
|
||||
plan = FlowPlan("")
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||
)
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
|
||||
)
|
||||
challenge = get_challenge_for_device(stage_view, stage, webauthn_device)
|
||||
challenge = get_challenge_for_device(request, stage, webauthn_device)
|
||||
webauthn_challenge = request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
||||
self.assertEqual(
|
||||
challenge["allowCredentials"],
|
||||
[
|
||||
{
|
||||
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
"type": "public-key",
|
||||
}
|
||||
],
|
||||
)
|
||||
self.assertIsNotNone(challenge["challenge"])
|
||||
self.assertEqual(
|
||||
challenge["rpId"],
|
||||
"testserver",
|
||||
)
|
||||
self.assertEqual(
|
||||
challenge["timeout"],
|
||||
60000,
|
||||
)
|
||||
self.assertEqual(
|
||||
challenge["userVerification"],
|
||||
"preferred",
|
||||
challenge,
|
||||
{
|
||||
"allowCredentials": [
|
||||
{
|
||||
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
"type": "public-key",
|
||||
}
|
||||
],
|
||||
"challenge": bytes_to_base64url(webauthn_challenge),
|
||||
"rpId": "testserver",
|
||||
"timeout": 60000,
|
||||
"userVerification": "preferred",
|
||||
},
|
||||
)
|
||||
|
||||
def test_get_challenge_userless(self):
|
||||
@ -247,16 +228,18 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
sign_count=0,
|
||||
rp_id=generate_id(),
|
||||
)
|
||||
plan = FlowPlan("")
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
|
||||
challenge = get_webauthn_challenge_without_user(request, stage)
|
||||
webauthn_challenge = request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
||||
self.assertEqual(
|
||||
challenge,
|
||||
{
|
||||
"allowCredentials": [],
|
||||
"challenge": bytes_to_base64url(webauthn_challenge),
|
||||
"rpId": "testserver",
|
||||
"timeout": 60000,
|
||||
"userVerification": "preferred",
|
||||
},
|
||||
)
|
||||
challenge = get_webauthn_challenge_without_user(stage_view, stage)
|
||||
self.assertEqual(challenge["allowCredentials"], [])
|
||||
self.assertIsNotNone(challenge["challenge"])
|
||||
self.assertEqual(challenge["rpId"], "testserver")
|
||||
self.assertEqual(challenge["timeout"], 60000)
|
||||
self.assertEqual(challenge["userVerification"], "preferred")
|
||||
|
||||
def test_validate_challenge_unrestricted(self):
|
||||
"""Test webauthn authentication (unrestricted webauthn device)"""
|
||||
@ -292,10 +275,10 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
"last_used": None,
|
||||
}
|
||||
]
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
"aCC6ak_DP45xMH1qyxzUM5iC2xc4QthQb09v7m4qDBmY8FvWvhxFzSuFlDYQmclrh5fWS5q0TPxgJGF4vimcFQ"
|
||||
)
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
response = self.client.post(
|
||||
@ -369,10 +352,10 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
"last_used": None,
|
||||
}
|
||||
]
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
"aCC6ak_DP45xMH1qyxzUM5iC2xc4QthQb09v7m4qDBmY8FvWvhxFzSuFlDYQmclrh5fWS5q0TPxgJGF4vimcFQ"
|
||||
)
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
response = self.client.post(
|
||||
@ -450,10 +433,10 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
"last_used": None,
|
||||
}
|
||||
]
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||
)
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
response = self.client.post(
|
||||
@ -513,14 +496,17 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
not_configured_action=NotConfiguredAction.CONFIGURE,
|
||||
device_classes=[DeviceClasses.WEBAUTHN],
|
||||
)
|
||||
plan = FlowPlan(flow.pk.hex)
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
||||
)
|
||||
request = get_request("/")
|
||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||
)
|
||||
request.session.save()
|
||||
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=flow, current_stage=stage, plan=plan), request=request
|
||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
||||
)
|
||||
request.META["SERVER_NAME"] = "localhost"
|
||||
request.META["SERVER_PORT"] = "9000"
|
||||
|
@ -25,7 +25,6 @@ class AuthenticatorWebAuthnStageSerializer(StageSerializer):
|
||||
"resident_key_requirement",
|
||||
"device_type_restrictions",
|
||||
"device_type_restrictions_obj",
|
||||
"max_attempts",
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 5.1.11 on 2025-06-13 22:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"authentik_stages_authenticator_webauthn",
|
||||
"0012_webauthndevice_created_webauthndevice_last_updated_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="authenticatorwebauthnstage",
|
||||
name="max_attempts",
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
]
|
@ -84,8 +84,6 @@ class AuthenticatorWebAuthnStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
||||
|
||||
device_type_restrictions = models.ManyToManyField("WebAuthnDeviceType", blank=True)
|
||||
|
||||
max_attempts = models.PositiveIntegerField(default=0)
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[BaseSerializer]:
|
||||
from authentik.stages.authenticator_webauthn.api.stages import (
|
||||
|
@ -5,13 +5,12 @@ from uuid import UUID
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.request import QueryDict
|
||||
from django.utils.translation import gettext as __
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from webauthn import options_to_json
|
||||
from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
|
||||
from webauthn.helpers.exceptions import WebAuthnException
|
||||
from webauthn.helpers.exceptions import InvalidRegistrationResponse
|
||||
from webauthn.helpers.structs import (
|
||||
AttestationConveyancePreference,
|
||||
AuthenticatorAttachment,
|
||||
@ -42,8 +41,7 @@ from authentik.stages.authenticator_webauthn.models import (
|
||||
)
|
||||
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
||||
|
||||
PLAN_CONTEXT_WEBAUTHN_CHALLENGE = "goauthentik.io/stages/authenticator_webauthn/challenge"
|
||||
PLAN_CONTEXT_WEBAUTHN_ATTEMPT = "goauthentik.io/stages/authenticator_webauthn/attempt"
|
||||
SESSION_KEY_WEBAUTHN_CHALLENGE = "authentik/stages/authenticator_webauthn/challenge"
|
||||
|
||||
|
||||
class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge):
|
||||
@ -64,7 +62,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
|
||||
|
||||
def validate_response(self, response: dict) -> dict:
|
||||
"""Validate webauthn challenge response"""
|
||||
challenge = self.stage.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE]
|
||||
challenge = self.request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
||||
|
||||
try:
|
||||
registration: VerifiedRegistration = verify_registration_response(
|
||||
@ -73,7 +71,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
|
||||
expected_rp_id=get_rp_id(self.request),
|
||||
expected_origin=get_origin(self.request),
|
||||
)
|
||||
except WebAuthnException as exc:
|
||||
except InvalidRegistrationResponse as exc:
|
||||
self.stage.logger.warning("registration failed", exc=exc)
|
||||
raise ValidationError(f"Registration failed. Error: {exc}") from None
|
||||
|
||||
@ -116,10 +114,9 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
response_class = AuthenticatorWebAuthnChallengeResponse
|
||||
|
||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
# clear session variables prior to starting a new registration
|
||||
self.request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
||||
stage: AuthenticatorWebAuthnStage = self.executor.current_stage
|
||||
self.executor.plan.context.setdefault(PLAN_CONTEXT_WEBAUTHN_ATTEMPT, 0)
|
||||
# clear flow variables prior to starting a new registration
|
||||
self.executor.plan.context.pop(PLAN_CONTEXT_WEBAUTHN_CHALLENGE, None)
|
||||
user = self.get_pending_user()
|
||||
|
||||
# library accepts none so we store null in the database, but if there is a value
|
||||
@ -142,7 +139,8 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
attestation=AttestationConveyancePreference.DIRECT,
|
||||
)
|
||||
|
||||
self.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = registration_options.challenge
|
||||
self.request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = registration_options.challenge
|
||||
self.request.session.save()
|
||||
return AuthenticatorWebAuthnChallenge(
|
||||
data={
|
||||
"registration": loads(options_to_json(registration_options)),
|
||||
@ -155,24 +153,6 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
response.user = self.get_pending_user()
|
||||
return response
|
||||
|
||||
def challenge_invalid(self, response):
|
||||
stage: AuthenticatorWebAuthnStage = self.executor.current_stage
|
||||
self.executor.plan.context.setdefault(PLAN_CONTEXT_WEBAUTHN_ATTEMPT, 0)
|
||||
self.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_ATTEMPT] += 1
|
||||
if (
|
||||
stage.max_attempts > 0
|
||||
and self.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_ATTEMPT] >= stage.max_attempts
|
||||
):
|
||||
return self.executor.stage_invalid(
|
||||
__(
|
||||
"Exceeded maximum attempts. "
|
||||
"Contact your {brand} administrator for help.".format(
|
||||
brand=self.request.brand.branding_title
|
||||
)
|
||||
)
|
||||
)
|
||||
return super().challenge_invalid(response)
|
||||
|
||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||
# Webauthn Challenge has already been validated
|
||||
webauthn_credential: VerifiedRegistration = response.validated_data["response"]
|
||||
@ -199,3 +179,6 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
else:
|
||||
return self.executor.stage_invalid("Device with Credential ID already exists.")
|
||||
return self.executor.stage_ok()
|
||||
|
||||
def cleanup(self):
|
||||
self.request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
||||
|
@ -18,7 +18,7 @@ from authentik.stages.authenticator_webauthn.models import (
|
||||
WebAuthnDevice,
|
||||
WebAuthnDeviceType,
|
||||
)
|
||||
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
|
||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
||||
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
|
||||
|
||||
|
||||
@ -57,9 +57,6 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
)
|
||||
|
||||
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
session = self.client.session
|
||||
self.assertStageResponse(
|
||||
@ -73,7 +70,7 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
"name": self.user.username,
|
||||
"displayName": self.user.name,
|
||||
},
|
||||
"challenge": bytes_to_base64url(plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE]),
|
||||
"challenge": bytes_to_base64url(session[SESSION_KEY_WEBAUTHN_CHALLENGE]),
|
||||
"pubKeyCredParams": [
|
||||
{"type": "public-key", "alg": -7},
|
||||
{"type": "public-key", "alg": -8},
|
||||
@ -100,11 +97,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
"""Test registration"""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session.save()
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
@ -149,11 +146,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session.save()
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
@ -212,11 +209,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session.save()
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
@ -262,11 +259,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session.save()
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
@ -301,109 +298,3 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||
self.assertTrue(WebAuthnDevice.objects.filter(user=self.user).exists())
|
||||
|
||||
def test_register_max_retries(self):
|
||||
"""Test registration (exceeding max retries)"""
|
||||
self.stage.max_attempts = 2
|
||||
self.stage.save()
|
||||
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||
)
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
# first failed request
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
data={
|
||||
"component": "ak-stage-authenticator-webauthn",
|
||||
"response": {
|
||||
"id": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||
"rawId": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||
"type": "public-key",
|
||||
"registrationClientExtensions": "{}",
|
||||
"response": {
|
||||
"clientDataJSON": (
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmd"
|
||||
"lIjoiMDNYb2RpNTRnS3NmblA1STlWRmZoYUdYVlZFMk5VeV"
|
||||
"pwQkJYbnNfSkkteDZWOVJZMlR3MlFteFJKa2hoNzE3NEVrU"
|
||||
"mF6VW50SXdqTVZZOWJGRzYwTHciLCJvcmlnaW4iOiJodHRw"
|
||||
"Oi8vbG9jYWxob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmF"
|
||||
),
|
||||
"attestationObject": (
|
||||
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5Yg"
|
||||
"OjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAPv8MA"
|
||||
"cVTk7MjAtuAgVX170AFJKp5q1S5wxvjsLEjR5IoWGWjc-bp"
|
||||
"QECAyYgASFYIKtcZHPumH37XHs0IM1v3pUBRIqHVV_SE-Lq"
|
||||
"2zpJAOVXIlgg74Fg_WdB0kuLYqCKbxogkEPaVtR_iR3IyQFIJAXBzds"
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
SERVER_NAME="localhost",
|
||||
SERVER_PORT="9000",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
flow=self.flow,
|
||||
component="ak-stage-authenticator-webauthn",
|
||||
response_errors={
|
||||
"response": [
|
||||
{
|
||||
"string": (
|
||||
"Registration failed. Error: Unable to decode "
|
||||
"client_data_json bytes as JSON"
|
||||
),
|
||||
"code": "invalid",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
self.assertFalse(WebAuthnDevice.objects.filter(user=self.user).exists())
|
||||
|
||||
# Second failed request
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
data={
|
||||
"component": "ak-stage-authenticator-webauthn",
|
||||
"response": {
|
||||
"id": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||
"rawId": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||
"type": "public-key",
|
||||
"registrationClientExtensions": "{}",
|
||||
"response": {
|
||||
"clientDataJSON": (
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmd"
|
||||
"lIjoiMDNYb2RpNTRnS3NmblA1STlWRmZoYUdYVlZFMk5VeV"
|
||||
"pwQkJYbnNfSkkteDZWOVJZMlR3MlFteFJKa2hoNzE3NEVrU"
|
||||
"mF6VW50SXdqTVZZOWJGRzYwTHciLCJvcmlnaW4iOiJodHRw"
|
||||
"Oi8vbG9jYWxob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmF"
|
||||
),
|
||||
"attestationObject": (
|
||||
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5Yg"
|
||||
"OjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAPv8MA"
|
||||
"cVTk7MjAtuAgVX170AFJKp5q1S5wxvjsLEjR5IoWGWjc-bp"
|
||||
"QECAyYgASFYIKtcZHPumH37XHs0IM1v3pUBRIqHVV_SE-Lq"
|
||||
"2zpJAOVXIlgg74Fg_WdB0kuLYqCKbxogkEPaVtR_iR3IyQFIJAXBzds"
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
SERVER_NAME="localhost",
|
||||
SERVER_PORT="9000",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
flow=self.flow,
|
||||
component="ak-stage-access-denied",
|
||||
error_message=(
|
||||
"Exceeded maximum attempts. Contact your authentik administrator for help."
|
||||
),
|
||||
)
|
||||
self.assertFalse(WebAuthnDevice.objects.filter(user=self.user).exists())
|
||||
|
@ -21,7 +21,6 @@ from authentik.flows.models import FlowDesignation, FlowToken
|
||||
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.stages.email.flow import pickle_flow_token_for_email
|
||||
from authentik.stages.email.models import EmailStage
|
||||
@ -129,9 +128,8 @@ class EmailStageView(ChallengeStageView):
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message=_("Exception occurred while rendering E-mail template"),
|
||||
error=exception_to_string(exc),
|
||||
template=current_stage.template,
|
||||
).from_http(self.request)
|
||||
).with_exception(exc).from_http(self.request)
|
||||
raise StageInvalidException from exc
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Serializer for tenants models"""
|
||||
|
||||
from django_tenants.utils import get_public_schema_name
|
||||
from rest_framework.fields import JSONField
|
||||
from rest_framework.generics import RetrieveUpdateAPIView
|
||||
from rest_framework.permissions import SAFE_METHODS
|
||||
|
||||
@ -13,8 +12,6 @@ from authentik.tenants.models import Tenant
|
||||
class SettingsSerializer(ModelSerializer):
|
||||
"""Settings Serializer"""
|
||||
|
||||
footer_links = JSONField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Tenant
|
||||
fields = [
|
||||
|
@ -16,7 +16,6 @@ def check_embedded_outpost_disabled(app_configs, **kwargs):
|
||||
"Embedded outpost must be disabled when tenants API is enabled.",
|
||||
hint="Disable embedded outpost by setting outposts.disable_embedded_outpost to "
|
||||
"True, or disable the tenants API by setting tenants.enabled to False",
|
||||
id="ak.tenants.E001",
|
||||
)
|
||||
]
|
||||
return []
|
||||
|
@ -13310,12 +13310,6 @@
|
||||
"format": "uuid"
|
||||
},
|
||||
"title": "Device type restrictions"
|
||||
},
|
||||
"max_attempts": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2147483647,
|
||||
"title": "Max attempts"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
6
go.mod
6
go.mod
@ -6,7 +6,7 @@ require (
|
||||
beryju.io/ldap v0.1.0
|
||||
github.com/avast/retry-go/v4 v4.6.1
|
||||
github.com/coreos/go-oidc/v3 v3.14.1
|
||||
github.com/getsentry/sentry-go v0.34.0
|
||||
github.com/getsentry/sentry-go v0.33.0
|
||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||
github.com/go-ldap/ldap/v3 v3.4.11
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
@ -23,13 +23,13 @@ require (
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
github.com/redis/go-redis/v9 v9.10.0
|
||||
github.com/sethvargo/go-envconfig v1.3.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2025062.6
|
||||
goauthentik.io/api/v3 v3.2025062.3
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.15.0
|
||||
|
12
go.sum
12
go.sum
@ -71,8 +71,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/getsentry/sentry-go v0.34.0 h1:1FCHBVp8TfSc8L10zqSwXUZNiOSF+10qw4czjarTiY4=
|
||||
github.com/getsentry/sentry-go v0.34.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
|
||||
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
@ -251,8 +251,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
|
||||
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
@ -298,8 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2025062.6 h1:rlChhGP2vJufYCaTMb4sbRBEE1p2uL5T4HzMqF1AJ4A=
|
||||
goauthentik.io/api/v3 v3.2025062.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2025062.3 h1:syBOKigaHyX/8Rwmh9kOSF+TzsxOzmP5i7rsFwbemzA=
|
||||
goauthentik.io/api/v3 v3.2025062.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1019.2",
|
||||
"aws-cdk": "^2.1019.1",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1019.2",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1019.2.tgz",
|
||||
"integrity": "sha512-LkWZ3IKBkfCPTCu60t4Wb9JMSkb+0Uzk+HIxZeW5sFohq8bxDGV0OP1hcqEC2+KbVYRn7q+YhMeSJ/FOQcgpiw==",
|
||||
"version": "2.1019.1",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1019.1.tgz",
|
||||
"integrity": "sha512-G2jxKuTsYTrYZX80CDApCrKcZ+AuFxxd+b0dkb0KEkfUsela7RqrDGLm5wOzSCIc3iH6GocR8JDVZuJ+0nNuKg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1019.2",
|
||||
"aws-cdk": "^2.1019.1",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ from typing import Any
|
||||
from psycopg import Connection, Cursor, connect
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.config import CONFIG, django_db_config
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
LOGGER = get_logger()
|
||||
ADV_LOCK_UID = 1000
|
||||
@ -115,13 +115,9 @@ def run_migrations():
|
||||
execute_from_command_line(["", "migrate_schemas"])
|
||||
if CONFIG.get_bool("tenants.enabled", False):
|
||||
execute_from_command_line(["", "migrate_schemas", "--schema", "template", "--tenant"])
|
||||
# Run django system checks for all databases
|
||||
check_args = ["", "check"]
|
||||
for label in django_db_config(CONFIG).keys():
|
||||
check_args.append(f"--database={label}")
|
||||
if not CONFIG.get_bool("debug"):
|
||||
check_args.append("--deploy")
|
||||
execute_from_command_line(check_args)
|
||||
execute_from_command_line(
|
||||
["", "check"] + ([] if CONFIG.get_bool("debug") else ["--deploy"])
|
||||
)
|
||||
finally:
|
||||
release_lock(curr)
|
||||
curr.close()
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-06-19 00:10+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -109,6 +109,10 @@ msgstr ""
|
||||
msgid "User does not have access to application."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/devices.py
|
||||
msgid "Extra description not available"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr ""
|
||||
|
Binary file not shown.
214
packages/eslint-config/package-lock.json
generated
214
packages/eslint-config/package-lock.json
generated
@ -576,17 +576,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
|
||||
"integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/type-utils": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/type-utils": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@ -600,7 +600,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.35.0",
|
||||
"@typescript-eslint/parser": "^8.34.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
@ -616,16 +616,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
|
||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -641,14 +641,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
|
||||
"integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
||||
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.35.0",
|
||||
"@typescript-eslint/types": "^8.35.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
||||
"@typescript-eslint/types": "^8.34.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -663,14 +663,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
|
||||
"integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
||||
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0"
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -681,9 +681,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
|
||||
"integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -698,14 +698,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
|
||||
"integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@ -722,9 +722,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
|
||||
"integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
||||
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -736,16 +736,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
|
||||
"integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
||||
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.35.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"@typescript-eslint/project-service": "8.34.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -804,16 +804,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
|
||||
"integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
||||
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.35.0"
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -828,13 +828,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
|
||||
"integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
||||
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -920,19 +920,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
|
||||
"integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
|
||||
"integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.24.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"is-string": "^1.1.1",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
"es-abstract": "^1.23.2",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"is-string": "^1.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -1378,27 +1376,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.24.0",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
|
||||
"integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
|
||||
"version": "1.23.9",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
||||
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"array-buffer-byte-length": "^1.0.2",
|
||||
"arraybuffer.prototype.slice": "^1.0.4",
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"call-bound": "^1.0.3",
|
||||
"data-view-buffer": "^1.0.2",
|
||||
"data-view-byte-length": "^1.0.2",
|
||||
"data-view-byte-offset": "^1.0.1",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"es-to-primitive": "^1.3.0",
|
||||
"function.prototype.name": "^1.1.8",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"get-proto": "^1.0.1",
|
||||
"get-intrinsic": "^1.2.7",
|
||||
"get-proto": "^1.0.0",
|
||||
"get-symbol-description": "^1.1.0",
|
||||
"globalthis": "^1.0.4",
|
||||
"gopd": "^1.2.0",
|
||||
@ -1410,24 +1408,21 @@
|
||||
"is-array-buffer": "^3.0.5",
|
||||
"is-callable": "^1.2.7",
|
||||
"is-data-view": "^1.0.2",
|
||||
"is-negative-zero": "^2.0.3",
|
||||
"is-regex": "^1.2.1",
|
||||
"is-set": "^2.0.3",
|
||||
"is-shared-array-buffer": "^1.0.4",
|
||||
"is-string": "^1.1.1",
|
||||
"is-typed-array": "^1.1.15",
|
||||
"is-weakref": "^1.1.1",
|
||||
"is-weakref": "^1.1.0",
|
||||
"math-intrinsics": "^1.1.0",
|
||||
"object-inspect": "^1.13.4",
|
||||
"object-inspect": "^1.13.3",
|
||||
"object-keys": "^1.1.1",
|
||||
"object.assign": "^4.1.7",
|
||||
"own-keys": "^1.0.1",
|
||||
"regexp.prototype.flags": "^1.5.4",
|
||||
"regexp.prototype.flags": "^1.5.3",
|
||||
"safe-array-concat": "^1.1.3",
|
||||
"safe-push-apply": "^1.0.0",
|
||||
"safe-regex-test": "^1.1.0",
|
||||
"set-proto": "^1.0.0",
|
||||
"stop-iteration-iterator": "^1.1.0",
|
||||
"string.prototype.trim": "^1.2.10",
|
||||
"string.prototype.trimend": "^1.0.9",
|
||||
"string.prototype.trimstart": "^1.0.8",
|
||||
@ -1436,7 +1431,7 @@
|
||||
"typed-array-byte-offset": "^1.0.4",
|
||||
"typed-array-length": "^1.0.7",
|
||||
"unbox-primitive": "^1.1.0",
|
||||
"which-typed-array": "^1.1.19"
|
||||
"which-typed-array": "^1.1.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -1639,9 +1634,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-module-utils": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
|
||||
"integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
|
||||
"integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^3.2.7"
|
||||
@ -1665,29 +1660,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import": {
|
||||
"version": "2.32.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"version": "2.31.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
|
||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
"array.prototype.findlastindex": "^1.2.6",
|
||||
"array.prototype.flat": "^1.3.3",
|
||||
"array.prototype.flatmap": "^1.3.3",
|
||||
"array-includes": "^3.1.8",
|
||||
"array.prototype.findlastindex": "^1.2.5",
|
||||
"array.prototype.flat": "^1.3.2",
|
||||
"array.prototype.flatmap": "^1.3.2",
|
||||
"debug": "^3.2.7",
|
||||
"doctrine": "^2.1.0",
|
||||
"eslint-import-resolver-node": "^0.3.9",
|
||||
"eslint-module-utils": "^2.12.1",
|
||||
"eslint-module-utils": "^2.12.0",
|
||||
"hasown": "^2.0.2",
|
||||
"is-core-module": "^2.16.1",
|
||||
"is-core-module": "^2.15.1",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^3.1.2",
|
||||
"object.fromentries": "^2.0.8",
|
||||
"object.groupby": "^1.0.3",
|
||||
"object.values": "^1.2.1",
|
||||
"object.values": "^1.2.0",
|
||||
"semver": "^6.3.1",
|
||||
"string.prototype.trimend": "^1.0.9",
|
||||
"string.prototype.trimend": "^1.0.8",
|
||||
"tsconfig-paths": "^3.15.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -2506,18 +2501,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-negative-zero": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
|
||||
"integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
@ -3710,19 +3693,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/stop-iteration-iterator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
||||
"integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"internal-slot": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.matchall": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
|
||||
@ -4065,15 +4035,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
|
||||
"integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
||||
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.35.0",
|
||||
"@typescript-eslint/parser": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||
"@typescript-eslint/parser": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
@ -2,11 +2,8 @@
|
||||
* @file Prettier configuration for authentik.
|
||||
*
|
||||
* @import { Config as PrettierConfig } from "prettier";
|
||||
*/
|
||||
|
||||
import { importsPlugin } from "./imports.js";
|
||||
|
||||
/**
|
||||
* @import { PluginConfig as SortPluginConfig } from "@trivago/prettier-plugin-sort-imports";
|
||||
*
|
||||
* @typedef {object} PackageJSONPluginConfig
|
||||
* @property {string[]} [packageSortOrder] Custom ordering array.
|
||||
*/
|
||||
@ -14,7 +11,7 @@ import { importsPlugin } from "./imports.js";
|
||||
/**
|
||||
* authentik Prettier configuration.
|
||||
*
|
||||
* @type {PrettierConfig & PackageJSONPluginConfig}
|
||||
* @type {PrettierConfig & SortPluginConfig & PackageJSONPluginConfig}
|
||||
* @internal
|
||||
*/
|
||||
export const AuthentikPrettierConfig = {
|
||||
@ -37,8 +34,32 @@ export const AuthentikPrettierConfig = {
|
||||
plugins: [
|
||||
// ---
|
||||
"prettier-plugin-packagejson",
|
||||
importsPlugin(),
|
||||
"@trivago/prettier-plugin-sort-imports",
|
||||
],
|
||||
importOrder: [
|
||||
// ---
|
||||
|
||||
"^(@goauthentik/|#)common.+",
|
||||
"^(@goauthentik/|#)elements.+",
|
||||
"^(@goauthentik/|#)components.+",
|
||||
"^(@goauthentik/|#)user.+",
|
||||
"^(@goauthentik/|#)admin.+",
|
||||
"^(@goauthentik/|#)flow.+",
|
||||
|
||||
"^#.+",
|
||||
"^@goauthentik.+",
|
||||
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
|
||||
"^(@?)lit(.*)$",
|
||||
"\\.css$",
|
||||
"^@goauthentik/api$",
|
||||
"^[./]",
|
||||
],
|
||||
importOrderSideEffects: false,
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true,
|
||||
importOrderParserPlugins: ["typescript", "jsx", "classProperties", "decorators-legacy"],
|
||||
overrides: [
|
||||
{
|
||||
files: "schemas/**/*.json",
|
||||
|
@ -1,172 +0,0 @@
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
import { formatSourceFromFile } from "format-imports";
|
||||
import { parsers as babelParsers } from "prettier/plugins/babel";
|
||||
/**
|
||||
* @file Prettier import plugin.
|
||||
*
|
||||
* @import { Plugin, ParserOptions } from "prettier";
|
||||
*/
|
||||
import { parsers as typescriptParsers } from "prettier/plugins/typescript";
|
||||
|
||||
const require = createRequire(process.cwd() + "/");
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function resolveModule(name) {
|
||||
try {
|
||||
return require.resolve(name);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const webSubmodules = [
|
||||
// ---
|
||||
"common",
|
||||
"elements",
|
||||
"components",
|
||||
"user",
|
||||
"admin",
|
||||
"flow",
|
||||
];
|
||||
|
||||
/**
|
||||
* Ensure that every import without an extension adds one.
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
function normalizeExtensions(input) {
|
||||
return input.replace(/(?:import|from)\s*["']((?:\.\.?\/).*?)(?<!\.\w+)["']/gm, (line, path) => {
|
||||
return line.replace(path, `${path}.js`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filepath
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
function normalizeImports(filepath, input) {
|
||||
let output = input;
|
||||
|
||||
// Replace all TypeScript imports with the paths resolved by Node/Browser import maps.
|
||||
|
||||
for (const submodule of webSubmodules) {
|
||||
const legacyPattern = new RegExp(
|
||||
[
|
||||
// ---
|
||||
`(?:import|from)`,
|
||||
`\\\(?\\n?\\s*`,
|
||||
`"(?<suffix>@goauthentik\/${submodule}\/)`,
|
||||
|
||||
`(?<path>[^"'.]+)`,
|
||||
`(?:.[^"']+)?["']`,
|
||||
`\\n?\\s*\\\)?;`,
|
||||
].join(""),
|
||||
"gm",
|
||||
);
|
||||
|
||||
output = output.replace(
|
||||
legacyPattern,
|
||||
/**
|
||||
* @param {string} line
|
||||
* @param {string} suffix
|
||||
* @param {string} path
|
||||
*/
|
||||
(line, suffix, path) => {
|
||||
const exported = `@goauthentik/web/${submodule}/${path}`;
|
||||
let imported = `#${submodule}/${path}`;
|
||||
|
||||
let module = resolveModule(`${exported}.ts`);
|
||||
|
||||
if (!module) {
|
||||
module = resolveModule(`${exported}/index.ts`);
|
||||
imported += "/index";
|
||||
}
|
||||
|
||||
if (imported.endsWith(".css")) {
|
||||
imported += ".js";
|
||||
}
|
||||
|
||||
if (!module) {
|
||||
console.warn(`\nCannot resolve module ${exported} from ${filepath}`, {
|
||||
line,
|
||||
path,
|
||||
exported,
|
||||
imported,
|
||||
module,
|
||||
});
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return (
|
||||
line
|
||||
// ---
|
||||
.replace(suffix + path, imported)
|
||||
.replace(`${imported}.js`, imported)
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Plugin}
|
||||
*/
|
||||
export function importsPlugin({
|
||||
useLegacyCleanup = process.env.AK_FIX_LEGACY_IMPORTS === "true",
|
||||
} = {}) {
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {ParserOptions} options
|
||||
*/
|
||||
const preprocess = (input, { filepath, printWidth }) => {
|
||||
let output = input;
|
||||
|
||||
if (useLegacyCleanup) {
|
||||
output = normalizeExtensions(input);
|
||||
output = normalizeImports(filepath, output);
|
||||
}
|
||||
|
||||
const value = formatSourceFromFile.sync(output, filepath, {
|
||||
nodeProtocol: "always",
|
||||
maxLineLength: printWidth,
|
||||
wrappingStyle: "prettier",
|
||||
groupRules: [
|
||||
"^node:",
|
||||
...webSubmodules.map((submodule) => `^(@goauthentik/|#)${submodule}.+`),
|
||||
|
||||
"^#.+",
|
||||
"^@goauthentik.+",
|
||||
|
||||
{}, // Other imports.
|
||||
|
||||
"^(@?)lit(.*)$",
|
||||
"\\.css$",
|
||||
"^@goauthentik/api$",
|
||||
"^[./]",
|
||||
],
|
||||
});
|
||||
|
||||
return value || input;
|
||||
};
|
||||
|
||||
return {
|
||||
parsers: {
|
||||
typescript: {
|
||||
...typescriptParsers.typescript,
|
||||
preprocess,
|
||||
},
|
||||
babel: {
|
||||
...babelParsers.babel,
|
||||
preprocess,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
1830
packages/prettier-config/package-lock.json
generated
1830
packages/prettier-config/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/prettier-config",
|
||||
"version": "3.0.0",
|
||||
"version": "2.0.1",
|
||||
"description": "authentik's Prettier config",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@ -10,19 +10,19 @@
|
||||
},
|
||||
"type": "module",
|
||||
"exports": "./index.js",
|
||||
"dependencies": {
|
||||
"format-imports": "^4.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goauthentik/tsconfig": "^1.0.1",
|
||||
"@types/node": "^24.0.4",
|
||||
"prettier": "^3.6.1",
|
||||
"prettier-plugin-packagejson": "^2.5.16",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.15",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prettier": "^3.6.1",
|
||||
"prettier-plugin-packagejson": "^2.5.16"
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
|
@ -17,10 +17,10 @@ dependencies = [
|
||||
"django-countries==7.6.1",
|
||||
"django-cte==2.0.0",
|
||||
"django-filter==25.1",
|
||||
"django-guardian==3.0.3",
|
||||
"django-guardian==3.0.0",
|
||||
"django-model-utils==5.0.0",
|
||||
"django-pglock==1.7.2",
|
||||
"django-prometheus==2.4.1",
|
||||
"django-prometheus==2.4.0",
|
||||
"django-redis==6.0.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-tenants==3.8.0",
|
||||
@ -36,7 +36,7 @@ dependencies = [
|
||||
"flower==2.0.1",
|
||||
"geoip2==5.1.0",
|
||||
"geopy==2.4.1",
|
||||
"google-api-python-client==2.174.0",
|
||||
"google-api-python-client==2.173.0",
|
||||
"gssapi==1.9.0",
|
||||
"gunicorn==23.0.0",
|
||||
"jsonpatch==1.33",
|
||||
@ -44,7 +44,7 @@ dependencies = [
|
||||
"kubernetes==33.1.0",
|
||||
"ldap3==2.9.1",
|
||||
"lxml==5.4.0",
|
||||
"msgraph-sdk==1.35.0",
|
||||
"msgraph-sdk==1.34.0",
|
||||
"opencontainers==0.0.14",
|
||||
"packaging==25.0",
|
||||
"paramiko==3.5.1",
|
||||
@ -57,7 +57,7 @@ dependencies = [
|
||||
"pyyaml==6.0.2",
|
||||
"requests-oauthlib==2.0.0",
|
||||
"scim2-filter-parser==0.7.0",
|
||||
"sentry-sdk==2.31.0",
|
||||
"sentry-sdk==2.30.0",
|
||||
"service-identity==24.2.0",
|
||||
"setproctitle==1.3.6",
|
||||
"structlog==25.4.0",
|
||||
|
221
schema.yml
221
schema.yml
@ -34963,10 +34963,6 @@ paths:
|
||||
name: friendly_name
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: max_attempts
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
@ -41338,9 +41334,7 @@ components:
|
||||
app:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- app
|
||||
- name
|
||||
@ -41355,9 +41349,7 @@ components:
|
||||
app:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- app
|
||||
- name
|
||||
@ -41946,9 +41938,7 @@ components:
|
||||
friendly_name:
|
||||
type: string
|
||||
nullable: true
|
||||
credentials:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
credentials: {}
|
||||
required:
|
||||
- component
|
||||
- credentials
|
||||
@ -41978,9 +41968,7 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
minLength: 1
|
||||
credentials:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
credentials: {}
|
||||
required:
|
||||
- credentials
|
||||
- name
|
||||
@ -42645,10 +42633,6 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/WebAuthnDeviceType'
|
||||
readOnly: true
|
||||
max_attempts:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
minimum: 0
|
||||
required:
|
||||
- component
|
||||
- device_type_restrictions_obj
|
||||
@ -42691,10 +42675,6 @@ components:
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
max_attempts:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
minimum: 0
|
||||
required:
|
||||
- name
|
||||
AuthorizationCodeAuthMethodEnum:
|
||||
@ -42785,9 +42765,7 @@ components:
|
||||
path:
|
||||
type: string
|
||||
default: ''
|
||||
context:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
context: {}
|
||||
last_applied:
|
||||
type: string
|
||||
format: date-time
|
||||
@ -42807,8 +42785,6 @@ components:
|
||||
type: string
|
||||
readOnly: true
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
readOnly: true
|
||||
content:
|
||||
type: string
|
||||
@ -42830,9 +42806,7 @@ components:
|
||||
path:
|
||||
type: string
|
||||
default: ''
|
||||
context:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
context: {}
|
||||
enabled:
|
||||
type: boolean
|
||||
content:
|
||||
@ -42912,9 +42886,7 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Certificates used for client authentication.
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- brand_uuid
|
||||
- domain
|
||||
@ -42984,9 +42956,7 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Certificates used for client authentication.
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- domain
|
||||
Cache:
|
||||
@ -43971,7 +43941,7 @@ components:
|
||||
- name
|
||||
Device:
|
||||
type: object
|
||||
description: Serializer for authenticator devices
|
||||
description: Serializer for Duo authenticator devices
|
||||
properties:
|
||||
verbose_name:
|
||||
type: string
|
||||
@ -44010,18 +43980,11 @@ components:
|
||||
nullable: true
|
||||
extra_description:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Get extra description
|
||||
readOnly: true
|
||||
external_id:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Get external Device ID
|
||||
readOnly: true
|
||||
required:
|
||||
- confirmed
|
||||
- created
|
||||
- external_id
|
||||
- extra_description
|
||||
- last_updated
|
||||
- last_used
|
||||
@ -44627,9 +44590,7 @@ components:
|
||||
$ref: '#/components/schemas/ProtocolEnum'
|
||||
host:
|
||||
type: string
|
||||
settings:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
settings: {}
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -44700,9 +44661,7 @@ components:
|
||||
host:
|
||||
type: string
|
||||
minLength: 1
|
||||
settings:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
settings: {}
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -44766,16 +44725,12 @@ components:
|
||||
format: uuid
|
||||
readOnly: true
|
||||
title: Event uuid
|
||||
user:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
user: {}
|
||||
action:
|
||||
$ref: '#/components/schemas/EventActions'
|
||||
app:
|
||||
type: string
|
||||
context:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
context: {}
|
||||
client_ip:
|
||||
type: string
|
||||
nullable: true
|
||||
@ -44786,9 +44741,7 @@ components:
|
||||
expires:
|
||||
type: string
|
||||
format: date-time
|
||||
brand:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
brand: {}
|
||||
required:
|
||||
- action
|
||||
- app
|
||||
@ -44933,17 +44886,13 @@ components:
|
||||
type: object
|
||||
description: Event Serializer
|
||||
properties:
|
||||
user:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
user: {}
|
||||
action:
|
||||
$ref: '#/components/schemas/EventActions'
|
||||
app:
|
||||
type: string
|
||||
minLength: 1
|
||||
context:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
context: {}
|
||||
client_ip:
|
||||
type: string
|
||||
nullable: true
|
||||
@ -44951,9 +44900,7 @@ components:
|
||||
expires:
|
||||
type: string
|
||||
format: date-time
|
||||
brand:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
brand: {}
|
||||
required:
|
||||
- action
|
||||
- app
|
||||
@ -45928,9 +45875,7 @@ components:
|
||||
type: string
|
||||
format: email
|
||||
maxLength: 254
|
||||
credentials:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
credentials: {}
|
||||
scopes:
|
||||
type: string
|
||||
exclude_users_service_account:
|
||||
@ -45981,8 +45926,6 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
readOnly: true
|
||||
required:
|
||||
- attributes
|
||||
@ -46097,9 +46040,7 @@ components:
|
||||
format: email
|
||||
minLength: 1
|
||||
maxLength: 254
|
||||
credentials:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
credentials: {}
|
||||
scopes:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -46144,8 +46085,6 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
readOnly: true
|
||||
required:
|
||||
- attributes
|
||||
@ -47474,8 +47413,6 @@ components:
|
||||
description: Return internal model name
|
||||
readOnly: true
|
||||
kubeconfig:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
description: Paste your kubeconfig here. authentik will automatically use
|
||||
the currently selected context.
|
||||
verify_ssl:
|
||||
@ -47500,8 +47437,6 @@ components:
|
||||
description: If enabled, use the local connection. Required Docker socket/Kubernetes
|
||||
Integration
|
||||
kubeconfig:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
description: Paste your kubeconfig here. authentik will automatically use
|
||||
the currently selected context.
|
||||
verify_ssl:
|
||||
@ -48438,8 +48373,6 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
readOnly: true
|
||||
required:
|
||||
- attributes
|
||||
@ -48596,8 +48529,6 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
readOnly: true
|
||||
required:
|
||||
- attributes
|
||||
@ -49510,9 +49441,7 @@ components:
|
||||
type: string
|
||||
oidc_jwks_url:
|
||||
type: string
|
||||
oidc_jwks:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
oidc_jwks: {}
|
||||
authorization_code_auth_method:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
|
||||
@ -49686,9 +49615,7 @@ components:
|
||||
type: string
|
||||
oidc_jwks_url:
|
||||
type: string
|
||||
oidc_jwks:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
oidc_jwks: {}
|
||||
authorization_code_auth_method:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
|
||||
@ -52373,9 +52300,7 @@ components:
|
||||
app:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
PatchedApplicationRequest:
|
||||
type: object
|
||||
description: Application Serializer
|
||||
@ -52527,9 +52452,7 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
minLength: 1
|
||||
credentials:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
credentials: {}
|
||||
PatchedAuthenticatorSMSStageRequest:
|
||||
type: object
|
||||
description: AuthenticatorSMSStage Serializer
|
||||
@ -52702,10 +52625,6 @@ components:
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
max_attempts:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
minimum: 0
|
||||
PatchedBlueprintInstanceRequest:
|
||||
type: object
|
||||
description: Info about a single blueprint instance file
|
||||
@ -52716,9 +52635,7 @@ components:
|
||||
path:
|
||||
type: string
|
||||
default: ''
|
||||
context:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
context: {}
|
||||
enabled:
|
||||
type: boolean
|
||||
content:
|
||||
@ -52789,9 +52706,7 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Certificates used for client authentication.
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
PatchedCaptchaStageRequest:
|
||||
type: object
|
||||
description: CaptchaStage Serializer
|
||||
@ -53067,9 +52982,7 @@ components:
|
||||
host:
|
||||
type: string
|
||||
minLength: 1
|
||||
settings:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
settings: {}
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
@ -53121,17 +53034,13 @@ components:
|
||||
type: object
|
||||
description: Event Serializer
|
||||
properties:
|
||||
user:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
user: {}
|
||||
action:
|
||||
$ref: '#/components/schemas/EventActions'
|
||||
app:
|
||||
type: string
|
||||
minLength: 1
|
||||
context:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
context: {}
|
||||
client_ip:
|
||||
type: string
|
||||
nullable: true
|
||||
@ -53139,9 +53048,7 @@ components:
|
||||
expires:
|
||||
type: string
|
||||
format: date-time
|
||||
brand:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
brand: {}
|
||||
PatchedExpressionPolicyRequest:
|
||||
type: object
|
||||
description: Group Membership Policy Serializer
|
||||
@ -53324,9 +53231,7 @@ components:
|
||||
format: email
|
||||
minLength: 1
|
||||
maxLength: 254
|
||||
credentials:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
credentials: {}
|
||||
scopes:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -53710,8 +53615,6 @@ components:
|
||||
description: If enabled, use the local connection. Required Docker socket/Kubernetes
|
||||
Integration
|
||||
kubeconfig:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
description: Paste your kubeconfig here. authentik will automatically use
|
||||
the currently selected context.
|
||||
verify_ssl:
|
||||
@ -54295,9 +54198,7 @@ components:
|
||||
type: string
|
||||
oidc_jwks_url:
|
||||
type: string
|
||||
oidc_jwks:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
oidc_jwks: {}
|
||||
authorization_code_auth_method:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
|
||||
@ -54776,9 +54677,7 @@ components:
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
settings:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
settings: {}
|
||||
connection_expiry:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -55235,9 +55134,7 @@ components:
|
||||
source:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
PatchedSCIMSourcePropertyMappingRequest:
|
||||
type: object
|
||||
description: SCIMSourcePropertyMapping Serializer
|
||||
@ -55298,9 +55195,7 @@ components:
|
||||
source:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
PatchedSMSDeviceRequest:
|
||||
type: object
|
||||
description: Serializer for sms authenticator devices
|
||||
@ -55387,7 +55282,9 @@ components:
|
||||
minimum: 0
|
||||
description: Reputation cannot increase higher than this value. Zero or
|
||||
positive.
|
||||
footer_links: {}
|
||||
footer_links:
|
||||
description: The option configures the footer links on the flow executor
|
||||
pages.
|
||||
gdpr_compliance:
|
||||
type: boolean
|
||||
description: When enabled, all the events caused by a user will be deleted
|
||||
@ -57199,9 +57096,7 @@ components:
|
||||
type: string
|
||||
description: Return internal model name
|
||||
readOnly: true
|
||||
settings:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
settings: {}
|
||||
outpost_set:
|
||||
type: array
|
||||
items:
|
||||
@ -57249,9 +57144,7 @@ components:
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
settings:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
settings: {}
|
||||
connection_expiry:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -57661,12 +57554,8 @@ components:
|
||||
type: string
|
||||
ip:
|
||||
type: string
|
||||
ip_geo_data:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
ip_asn_data:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
ip_geo_data: {}
|
||||
ip_asn_data: {}
|
||||
score:
|
||||
type: integer
|
||||
maximum: 9223372036854775807
|
||||
@ -58739,8 +58628,6 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
readOnly: true
|
||||
required:
|
||||
- attributes
|
||||
@ -58831,8 +58718,6 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
readOnly: true
|
||||
required:
|
||||
- attributes
|
||||
@ -58947,9 +58832,7 @@ components:
|
||||
source:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- group
|
||||
- group_obj
|
||||
@ -58968,9 +58851,7 @@ components:
|
||||
source:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- group
|
||||
- id
|
||||
@ -59089,9 +58970,7 @@ components:
|
||||
source:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- id
|
||||
- source
|
||||
@ -59109,9 +58988,7 @@ components:
|
||||
source:
|
||||
type: string
|
||||
format: uuid
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
attributes: {}
|
||||
required:
|
||||
- id
|
||||
- source
|
||||
@ -59504,7 +59381,9 @@ components:
|
||||
minimum: 0
|
||||
description: Reputation cannot increase higher than this value. Zero or
|
||||
positive.
|
||||
footer_links: {}
|
||||
footer_links:
|
||||
description: The option configures the footer links on the flow executor
|
||||
pages.
|
||||
gdpr_compliance:
|
||||
type: boolean
|
||||
description: When enabled, all the events caused by a user will be deleted
|
||||
@ -59556,7 +59435,9 @@ components:
|
||||
minimum: 0
|
||||
description: Reputation cannot increase higher than this value. Zero or
|
||||
positive.
|
||||
footer_links: {}
|
||||
footer_links:
|
||||
description: The option configures the footer links on the flow executor
|
||||
pages.
|
||||
gdpr_compliance:
|
||||
type: boolean
|
||||
description: When enabled, all the events caused by a user will be deleted
|
||||
|
@ -9,8 +9,8 @@
|
||||
"strict": true,
|
||||
"newLine": "lf",
|
||||
"target": "ESNext",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "dist",
|
||||
"skipDefaultLibCheck": true,
|
||||
"skipLibCheck": true,
|
||||
|
@ -7,7 +7,7 @@ services:
|
||||
network_mode: host
|
||||
restart: always
|
||||
mailpit:
|
||||
image: docker.io/axllent/mailpit:v1.26.2
|
||||
image: docker.io/axllent/mailpit:v1.26.1
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
|
8
tests/manual/openid-conformance/README.md
Normal file
8
tests/manual/openid-conformance/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# #Test files for OpenID Conformance testing.
|
||||
|
||||
These config files assume testing is being done using the [OpenID Conformance Suite
|
||||
](https://openid.net/certification/about-conformance-suite/), locally.
|
||||
|
||||
See https://gitlab.com/openid/conformance-suite/-/wikis/Developers/Build-&-Run for running the conformance suite locally.
|
||||
|
||||
Requires docker containers to be able to access the host via `host.docker.internal` and an entry in the hosts file that maps `host.docker.internal` to localhost.
|
@ -1,8 +1,6 @@
|
||||
version: 1
|
||||
metadata:
|
||||
name: OpenID Conformance testing
|
||||
labels:
|
||||
blueprints.goauthentik.io/instantiate: "false"
|
||||
name: OIDC conformance testing
|
||||
entries:
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/oauth2/scope-address
|
||||
@ -23,72 +21,38 @@ entries:
|
||||
attrs:
|
||||
name: "authentik default OAuth Mapping: OpenID 'phone'"
|
||||
scope_name: phone
|
||||
description: "General phone information"
|
||||
description: "General phone Information"
|
||||
expression: |
|
||||
return {
|
||||
"phone_number": "+1234",
|
||||
"phone_number_verified": True,
|
||||
}
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/oauth2/scope-profile-oidc-standard
|
||||
model: authentik_providers_oauth2.scopemapping
|
||||
attrs:
|
||||
name: "OIDC conformance profile"
|
||||
scope_name: profile
|
||||
description: "General profile information"
|
||||
expression: |
|
||||
return {
|
||||
# Because authentik only saves the user's full name, and has no concept of first and last names,
|
||||
# the full name is used as given name.
|
||||
# You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`
|
||||
"name": request.user.name,
|
||||
"given_name": request.user.name,
|
||||
"preferred_username": request.user.username,
|
||||
"nickname": request.user.username,
|
||||
"groups": [group.name for group in request.user.ak_groups.all()],
|
||||
"website" : "foo",
|
||||
"zoneinfo" : "foo",
|
||||
"birthdate" : "2000",
|
||||
"gender" : "foo",
|
||||
"profile" : "foo",
|
||||
"middle_name" : "foo",
|
||||
"locale" : "foo",
|
||||
"picture" : "foo",
|
||||
"updated_at" : 1748557810,
|
||||
"family_name" : "foo",
|
||||
}
|
||||
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: oidc-conformance-1
|
||||
id: provider
|
||||
identifiers:
|
||||
name: oidc-conformance-1
|
||||
name: provider
|
||||
attrs:
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
# Required as OIDC Conformance test requires issues to be the same across multiple clients
|
||||
issuer_mode: global
|
||||
client_id: 4054d882aff59755f2f279968b97ce8806a926e1
|
||||
client_secret: 4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867
|
||||
redirect_uris:
|
||||
- matching_mode: strict
|
||||
url: https://localhost:8443/test/a/authentik/callback
|
||||
- matching_mode: strict
|
||||
url: https://host.docker.internal:8443/test/a/authentik/callback
|
||||
redirect_uris: |
|
||||
https://localhost:8443/test/a/authentik/callback
|
||||
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile-oidc-standard]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-offline_access]]
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
- model: authentik_core.application
|
||||
identifiers:
|
||||
slug: oidc-conformance-1
|
||||
slug: conformance
|
||||
attrs:
|
||||
provider: !KeyOf oidc-conformance-1
|
||||
name: OIDC Conformance (1)
|
||||
provider: !KeyOf provider
|
||||
name: Conformance
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: oidc-conformance-2
|
||||
@ -96,27 +60,22 @@ entries:
|
||||
name: oidc-conformance-2
|
||||
attrs:
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
# Required as OIDC Conformance test requires issues to be the same across multiple clients
|
||||
issuer_mode: global
|
||||
client_id: ad64aeaf1efe388ecf4d28fcc537e8de08bcae26
|
||||
client_secret: ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789
|
||||
redirect_uris:
|
||||
- matching_mode: strict
|
||||
url: https://localhost:8443/test/a/authentik/callback
|
||||
- matching_mode: strict
|
||||
url: https://host.docker.internal:8443/test/a/authentik/callback
|
||||
redirect_uris: |
|
||||
https://localhost:8443/test/a/authentik/callback
|
||||
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile-oidc-standard]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-offline_access]]
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
- model: authentik_core.application
|
||||
identifiers:
|
||||
slug: oidc-conformance-2
|
||||
attrs:
|
||||
provider: !KeyOf oidc-conformance-2
|
||||
name: OIDC Conformance (2)
|
||||
name: OIDC Conformance
|
20
tests/manual/openid-conformance/test-config.json
Normal file
20
tests/manual/openid-conformance/test-config.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"alias": "authentik",
|
||||
"description": "authentik",
|
||||
"server": {
|
||||
"discoveryUrl": "http://host.docker.internal:9000/application/o/conformance/.well-known/openid-configuration"
|
||||
},
|
||||
"client": {
|
||||
"client_id": "4054d882aff59755f2f279968b97ce8806a926e1",
|
||||
"client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867"
|
||||
},
|
||||
"client_secret_post": {
|
||||
"client_id": "4054d882aff59755f2f279968b97ce8806a926e1",
|
||||
"client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867"
|
||||
},
|
||||
"client2": {
|
||||
"client_id": "ad64aeaf1efe388ecf4d28fcc537e8de08bcae26",
|
||||
"client_secret": "ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789"
|
||||
},
|
||||
"consent": {}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:6.0.13
|
||||
httpd:
|
||||
image: ghcr.io/beryju/oidc-conformance-suite-httpd:v5.1.32
|
||||
ports:
|
||||
- "8443:8443"
|
||||
- "8444:8444"
|
||||
depends_on:
|
||||
- server
|
||||
server:
|
||||
image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.32
|
||||
ports:
|
||||
- "9999:9999"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
command: >
|
||||
java
|
||||
-Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n
|
||||
-jar /server/fapi-test-suite.jar
|
||||
-Djdk.tls.maxHandshakeMessageSize=65536
|
||||
--fintechlabs.base_url=https://host.docker.internal:8443
|
||||
--fintechlabs.base_mtls_url=https://host.docker.internal:8444
|
||||
--fintechlabs.devmode=true
|
||||
--fintechlabs.startredir=true
|
||||
links:
|
||||
- mongodb:mongodb
|
||||
depends_on:
|
||||
- mongodb
|
151
uv.lock
generated
151
uv.lock
generated
@ -279,10 +279,10 @@ requires-dist = [
|
||||
{ name = "django-countries", specifier = "==7.6.1" },
|
||||
{ name = "django-cte", specifier = "==2.0.0" },
|
||||
{ name = "django-filter", specifier = "==25.1" },
|
||||
{ name = "django-guardian", specifier = "==3.0.3" },
|
||||
{ name = "django-guardian", specifier = "==3.0.0" },
|
||||
{ name = "django-model-utils", specifier = "==5.0.0" },
|
||||
{ name = "django-pglock", specifier = "==1.7.2" },
|
||||
{ name = "django-prometheus", specifier = "==2.4.1" },
|
||||
{ name = "django-prometheus", specifier = "==2.4.0" },
|
||||
{ name = "django-redis", specifier = "==6.0.0" },
|
||||
{ name = "django-storages", extras = ["s3"], specifier = "==1.14.6" },
|
||||
{ name = "django-tenants", specifier = "==3.8.0" },
|
||||
@ -298,7 +298,7 @@ requires-dist = [
|
||||
{ name = "flower", specifier = "==2.0.1" },
|
||||
{ name = "geoip2", specifier = "==5.1.0" },
|
||||
{ name = "geopy", specifier = "==2.4.1" },
|
||||
{ name = "google-api-python-client", specifier = "==2.174.0" },
|
||||
{ name = "google-api-python-client", specifier = "==2.173.0" },
|
||||
{ name = "gssapi", specifier = "==1.9.0" },
|
||||
{ name = "gunicorn", specifier = "==23.0.0" },
|
||||
{ name = "jsonpatch", specifier = "==1.33" },
|
||||
@ -306,7 +306,7 @@ requires-dist = [
|
||||
{ name = "kubernetes", specifier = "==33.1.0" },
|
||||
{ name = "ldap3", specifier = "==2.9.1" },
|
||||
{ name = "lxml", specifier = "==5.4.0" },
|
||||
{ name = "msgraph-sdk", specifier = "==1.35.0" },
|
||||
{ name = "msgraph-sdk", specifier = "==1.34.0" },
|
||||
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
|
||||
{ name = "packaging", specifier = "==25.0" },
|
||||
{ name = "paramiko", specifier = "==3.5.1" },
|
||||
@ -319,7 +319,7 @@ requires-dist = [
|
||||
{ name = "pyyaml", specifier = "==6.0.2" },
|
||||
{ name = "requests-oauthlib", specifier = "==2.0.0" },
|
||||
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
|
||||
{ name = "sentry-sdk", specifier = "==2.31.0" },
|
||||
{ name = "sentry-sdk", specifier = "==2.30.0" },
|
||||
{ name = "service-identity", specifier = "==24.2.0" },
|
||||
{ name = "setproctitle", specifier = "==1.3.6" },
|
||||
{ name = "structlog", specifier = "==25.4.0" },
|
||||
@ -574,30 +574,30 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.38.43"
|
||||
version = "1.38.38"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
{ name = "jmespath" },
|
||||
{ name = "s3transfer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/90/96/c99c9dac902faae3896558809d130b1bf02df8abb6e4553ad87d018910f9/boto3-1.38.43.tar.gz", hash = "sha256:9b0ff0b34c9cf7328546c532c20b081f09055ff485f4d57c19146c36877048c5", size = 111845, upload-time = "2025-06-24T19:29:02.978Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/a1/f2b68cba5d1907e004f4d88a028eda35a4f619c1e81d764e5cf58491eb46/boto3-1.38.38.tar.gz", hash = "sha256:0fe6b7d1974851588ec1edd39c66d9525d539133e02c7f985f9ebec5e222c0db", size = 111847, upload-time = "2025-06-17T19:33:03.097Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/67/42355b452a5aa622205c321217cba61a85746f0d93984788116a43120821/boto3-1.38.43-py3-none-any.whl", hash = "sha256:2e3411bb43285caad1c8e1a3186d025ba65a6342e26bad493f6b8feb3d1a1680", size = 139922, upload-time = "2025-06-24T19:29:01.545Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/dc/43d4ab839b84876bdf7baeba0a3ffcef4c3d52d81f3ce1979b4195c0e213/boto3-1.38.38-py3-none-any.whl", hash = "sha256:6f4163cd9e030afd1059e8a6daa178835165b79eb0b5325a8cd447020b895921", size = 139934, upload-time = "2025-06-17T19:33:00.621Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.38.43"
|
||||
version = "1.38.38"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jmespath" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/ff/8ace3f46fa1a32c09ee994b5401c7853613a283e134449fdc136bb753b40/botocore-1.38.43.tar.gz", hash = "sha256:c453c5c16c157c5427058bb3cc2c5ad35ee2e43336f0ccbfcc6092c5635505c6", size = 14044468, upload-time = "2025-06-24T19:28:52.803Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/d05258ac4ae68769a956779192bfbd322e571ef9fc17a27f02d35c026b4b/botocore-1.38.38.tar.gz", hash = "sha256:acf9ae5b2d99c1f416f94fa5b4f8c044ecb76ffcb7fb1b1daec583f36892a8e2", size = 14009715, upload-time = "2025-06-17T19:32:52.705Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/12/0ebcfb91738d0cf9560220ee4e0db351acab14026fac74bbce9ab3881fd9/botocore-1.38.43-py3-none-any.whl", hash = "sha256:2ee60ac0b08e80e9be2aa2841d42e438d5bc4f82549560a682837655097a3db7", size = 13706448, upload-time = "2025-06-24T19:28:47.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/c6/74f27ffe941dc1438b7fef620b402b982a9f9ab90a04ee47bd0314a02384/botocore-1.38.38-py3-none-any.whl", hash = "sha256:aa5cc63bf885819d862852edb647d6276fe423c60113e8db375bb7ad8d88a5d9", size = 13669107, upload-time = "2025-06-17T19:32:47.503Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -777,14 +777,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "click-plugins"
|
||||
version = "1.1.1.2"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164, upload-time = "2019-04-04T04:27:04.82Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497, upload-time = "2019-04-04T04:27:03.36Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1021,14 +1021,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django-guardian"
|
||||
version = "3.0.3"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/c2/3ed43813dd7313f729dbaa829b4f9ed4a647530151f672cfb5f843c12edf/django_guardian-3.0.3.tar.gz", hash = "sha256:4e59eab4d836da5a027cf0c176d14bc2a4e22cbbdf753159a03946c08c8a196d", size = 85410, upload-time = "2025-06-25T20:42:17.475Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/82/2c76cdf77eae3cb0c3df394686daf8f84bcd604c0da7a26fa19f5fe74ed4/django_guardian-3.0.0.tar.gz", hash = "sha256:0c79d55c4af2cfc14fbd19539846a1ebfed2a38198b7697e0f5177b7f654e1cd", size = 79895, upload-time = "2025-05-07T19:33:23.328Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/13/e6f629a978ef5fab8b8d2760cacc3e451016cef952cf4c049d672c5c6b07/django_guardian-3.0.3-py3-none-any.whl", hash = "sha256:d2164cea9f03c369d7ade21802710f3ab23ca6734bcc7dfcfb385906783916c7", size = 118198, upload-time = "2025-06-25T20:42:15.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/81/a2f3d3245d1f4cf446d78863526fba0b1b140d60784095a5cc2d4e8ac709/django_guardian-3.0.0-py3-none-any.whl", hash = "sha256:f3ebe3cc7f486e267041b780c3429ad5db72c909df40c2f74adb1b059582a3cd", size = 112672, upload-time = "2025-05-07T19:33:21.719Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1070,15 +1070,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django-prometheus"
|
||||
version = "2.4.1"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "prometheus-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/f4/cb39ddd2a41e07a274c4e162c076e906ae232d63b66bbabdea0300878877/django_prometheus-2.4.1.tar.gz", hash = "sha256:073628243d2a6de6a8a8c20e5b512872dfb85d66e1b60b28bcf1eca0155dad95", size = 24464, upload-time = "2025-06-25T15:45:37.149Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/b9/c758675671d71a1800feaad5c5fbcdecbd8d34296b63f9dc5662db39abda/django_prometheus-2.4.0.tar.gz", hash = "sha256:67da5c73d8e859aa73f6e11f52341c482691b17f8bd9844157cff6cdf51ce9bc", size = 24393, upload-time = "2025-06-18T18:06:28.673Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/50/9c5e022fa92574e5d20606687f15a2aa255e10512a17d11a8216fa117f72/django_prometheus-2.4.1-py2.py3-none-any.whl", hash = "sha256:7fe5af7f7c9ad9cd8a429fe0f3f1bf651f0e244f77162147869eab7ec09cc5e7", size = 29541, upload-time = "2025-06-25T15:45:35.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/05/d980950fb8c3f6f96c644599b1a025fb50e827477b1acf36daef72aa7e76/django_prometheus-2.4.0-py2.py3-none-any.whl", hash = "sha256:5b46b5f07b02ba8dd7abdb03a3c39073e8fd9120e2293a1ecb949bbb865378ac", size = 29528, upload-time = "2025-06-18T18:06:27.079Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1403,7 +1402,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.174.0"
|
||||
version = "2.173.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-api-core" },
|
||||
@ -1412,9 +1411,9 @@ dependencies = [
|
||||
{ name = "httplib2" },
|
||||
{ name = "uritemplate" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/fd/860fef0cf3edbad828e2ab4d2ddee5dfe8e595b6da748ac6c77e95bc7bef/google_api_python_client-2.174.0.tar.gz", hash = "sha256:9eb7616a820b38a9c12c5486f9b9055385c7feb18b20cbafc5c5a688b14f3515", size = 13127872, upload-time = "2025-06-25T19:27:12.977Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/7e/7c6e43e54f611f0f97f1678ea567fe06fecd545bd574db05e204e5b136fe/google_api_python_client-2.173.0.tar.gz", hash = "sha256:b537bc689758f4be3e6f40d59a6c0cd305abafdea91af4bc66ec31d40c08c804", size = 13091318, upload-time = "2025-06-19T19:39:05.881Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/2d/4250b81e8f5309b58650660f403584db6f64067acac74475893a8f33348d/google_api_python_client-2.174.0-py3-none-any.whl", hash = "sha256:f695205ceec97bfaa1590a14282559c4109326c473b07352233a3584cdbf4b89", size = 13650466, upload-time = "2025-06-25T19:27:10.426Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/c9/dc9ca0537ee2ddac0f0b1e458903afe3f490a0f90dfd4b1b16eb339cdfbb/google_api_python_client-2.173.0-py3-none-any.whl", hash = "sha256:16a8e81c772dd116f5c4ee47d83643149e1367dc8fb4f47cb471fbcb5c7d7ac7", size = 13612778, upload-time = "2025-06-19T19:39:03.283Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2072,7 +2071,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.35.0"
|
||||
version = "1.34.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-identity" },
|
||||
@ -2082,50 +2081,54 @@ dependencies = [
|
||||
{ name = "microsoft-kiota-serialization-text" },
|
||||
{ name = "msgraph-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/33/49/25df000defb136542400bbe3096b3e1dab384e5b02fec4c6c4cb4a433296/msgraph_sdk-1.35.0.tar.gz", hash = "sha256:513f77d3332618af35d2f456ff26e2050f136abc8856858a69d63e811480eddd", size = 5967030, upload-time = "2025-06-25T10:28:30.599Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/7a/c69b4fc4b9c02a6d14eddc96b91319dd7e91f0987245d4243a74b9c17fcf/msgraph_sdk-1.34.0.tar.gz", hash = "sha256:f71a81d3291f49d3610220de47bbbb6321aa62f7129d17a958f301b9acadfe99", size = 5968516, upload-time = "2025-06-18T11:43:33.287Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/72/ae/a0ea8742af0c99c9f53d82bca19f027f10d747874f725fa2f8d165eb60b3/msgraph_sdk-1.35.0-py3-none-any.whl", hash = "sha256:0e2305a0d6d8343f3a29aa227183c6acc6191f4dfda8522ea41d97e7fe25a0d1", size = 24490922, upload-time = "2025-06-25T10:28:28.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/0c/75f8066eca60fe9b2d5e1dd868b592533671b7b5cc711e655afd5c44d259/msgraph_sdk-1.34.0-py3-none-any.whl", hash = "sha256:d6daea012b78a7a4dd07fabb782ae00e4a9fe4f8d6016e8037769962533aa8ae", size = 24491410, upload-time = "2025-06-18T11:43:30.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "6.5.1"
|
||||
version = "6.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/43/2d90c414d9efc4587d6e7cebae9f2c2d8001bcb4f89ed514ae837e9dcbe6/multidict-6.5.1.tar.gz", hash = "sha256:a835ea8103f4723915d7d621529c80ef48db48ae0c818afcabe0f95aa1febc3a", size = 98690, upload-time = "2025-06-24T22:16:05.117Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/b5/59f27b4ce9951a4bce56b88ba5ff5159486797ab18863f2b4c1c5e8465bd/multidict-6.5.0.tar.gz", hash = "sha256:942bd8002492ba819426a8d7aefde3189c1b87099cdf18aaaefefcf7f3f7b6d2", size = 98512, upload-time = "2025-06-17T14:15:56.556Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/3f/c2e07031111d2513d260157933a8697ad52a935d8a2a2b8b7b317ddd9a96/multidict-6.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:98011312f36d1e496f15454a95578d1212bc2ffc25650a8484752b06d304fd9b", size = 73588, upload-time = "2025-06-24T22:14:54.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/bb/f47aa21827202a9f889fd66de9a1db33d0e4bbaaa2567156e4efb3cc0e5e/multidict-6.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bae589fb902b47bd94e6f539b34eefe55a1736099f616f614ec1544a43f95b05", size = 43756, upload-time = "2025-06-24T22:14:55.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/ec/24549de092c9b0bc3167e0beb31a11be58e8595dbcfed2b7821795bb3923/multidict-6.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6eb3bf26cd94eb306e4bc776d0964cc67a7967e4ad9299309f0ff5beec3c62be", size = 42222, upload-time = "2025-06-24T22:14:57.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/45/54452027ebc0ba660667aab67ae11afb9aaba91f4b5d63cddef045279d94/multidict-6.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5e1a5a99c72d1531501406fcc06b6bf699ebd079dacd6807bb43fc0ff260e5c", size = 253014, upload-time = "2025-06-24T22:14:58.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/3c/76e7b4c0ce3a8bb43efca679674fba421333fbc8429134072db80e13dcb8/multidict-6.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:38755bcba18720cb2338bea23a5afcff234445ee75fa11518f6130e22f2ab970", size = 235939, upload-time = "2025-06-24T22:15:00.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ce/48e3123a9af61ff2f60e3764b0b15cf4fca22b1299aac281252ac3a590d6/multidict-6.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f42fef9bcba3c32fd4e4a23c5757fc807d218b249573aaffa8634879f95feb73", size = 262940, upload-time = "2025-06-24T22:15:01.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/ab/bccd739faf87051b55df619a0967c8545b4d4a4b90258c5f564ab1752f15/multidict-6.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:071b962f4cc87469cda90c7cc1c077b76496878b39851d7417a3d994e27fe2c6", size = 260652, upload-time = "2025-06-24T22:15:02.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/9c/01f654aad28a5d0d74f2678c1541ae15e711f99603fd84c780078205966e/multidict-6.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:627ba4b7ce7c0115981f0fd91921f5d101dfb9972622178aeef84ccce1c2bbf3", size = 250011, upload-time = "2025-06-24T22:15:04.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/bc/edf08906e1db7385c6bf36e4179957307f50c44a889493e9b251255be79c/multidict-6.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05dcaed3e5e54f0d0f99a39762b0195274b75016cbf246f600900305581cf1a2", size = 248242, upload-time = "2025-06-24T22:15:06.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/c3/1ad054b88b889fda8b62ea9634ac7082567e8dc42b9b794a2c565ef102ab/multidict-6.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:11f5ecf3e741a18c578d118ad257c5588ca33cc7c46d51c0487d7ae76f072c32", size = 244683, upload-time = "2025-06-24T22:15:07.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/63/119a76b2095e1bb765816175cafeac7b520f564691abef2572fb80f4f246/multidict-6.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b948eb625411c20b15088fca862c51a39140b9cf7875b5fb47a72bb249fa2f42", size = 257626, upload-time = "2025-06-24T22:15:09.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/a9/b91a76af5ff49bd088ee76d11eb6134227f5ea50bcd5f6738443b2fe8e05/multidict-6.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc993a96dfc8300befd03d03df46efdb1d8d5a46911b014e956a4443035f470d", size = 251077, upload-time = "2025-06-24T22:15:10.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/fe/b1dc57aaa4de9f5a27543e28bd1f8bff00a316888b7344b5d33258b14b0a/multidict-6.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2d333380f22d35a56c6461f4579cfe186e143cd0b010b9524ac027de2a34cd", size = 244715, upload-time = "2025-06-24T22:15:11.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/55/47a82690f71d0141eea49a623bbcc00a4d28770efc7cba8ead75602c9b90/multidict-6.5.1-cp313-cp313-win32.whl", hash = "sha256:5891e3327e6a426ddd443c87339b967c84feb8c022dd425e0c025fa0fcd71e68", size = 41156, upload-time = "2025-06-24T22:15:13.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/b3/43306e4d7d3a9898574d1dc156b9607540dad581b1d767c992030751b82d/multidict-6.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:fcdaa72261bff25fad93e7cb9bd7112bd4bac209148e698e380426489d8ed8a9", size = 44933, upload-time = "2025-06-24T22:15:14.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/e2/34cb83c8a4e01b28e2abf30dc90178aa63c9db042be22fa02472cb744b86/multidict-6.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:84292145303f354a35558e601c665cdf87059d87b12777417e2e57ba3eb98903", size = 41967, upload-time = "2025-06-24T22:15:15.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/08/17d2de9cf749ea9589ecfb7532ab4988e8b113b7624826dba6b7527a58f3/multidict-6.5.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f8316e58db799a1972afbc46770dfaaf20b0847003ab80de6fcb9861194faa3f", size = 80513, upload-time = "2025-06-24T22:15:16.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/b9/c9392465a21f7dff164633348b4cf66eef55c4ee48bdcdc00f0a71792779/multidict-6.5.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3468f0db187aca59eb56e0aa9f7c8c5427bcb844ad1c86557b4886aeb4484d8", size = 46854, upload-time = "2025-06-24T22:15:18.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/24/d79cbed5d0573304bc907dff0e5ad8788a4de891eec832809812b319930e/multidict-6.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:228533a5f99f1248cd79f6470779c424d63bc3e10d47c82511c65cc294458445", size = 45724, upload-time = "2025-06-24T22:15:19.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/22/232be6c077183719c78131f0e3c3d7134eb2d839e6e50e1c1e69e5ef5965/multidict-6.5.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527076fdf5854901b1246c589af9a8a18b4a308375acb0020b585f696a10c794", size = 251895, upload-time = "2025-06-24T22:15:20.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/80/85985e1441864b946e79538355b7b47f36206bf6bbaa2fa6d74d8232f2ab/multidict-6.5.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9a17a17bad5c22f43e6a6b285dd9c16b1e8f8428202cd9bc22adaac68d0bbfed", size = 229357, upload-time = "2025-06-24T22:15:21.949Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/14/0024d1428b05aedaeea211da232aa6b6ad5c556a8a38b0942df1e54e1fa5/multidict-6.5.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:efd1951edab4a6cb65108d411867811f2b283f4b972337fb4269e40142f7f6a6", size = 259262, upload-time = "2025-06-24T22:15:23.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/cc/3fe63d61ffc9a48d62f36249e228e330144d990ac01f61169b615a3be471/multidict-6.5.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c07d5f38b39acb4f8f61a7aa4166d140ed628245ff0441630df15340532e3b3c", size = 257998, upload-time = "2025-06-24T22:15:24.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/e4/46b38b9a565ccc5d86f55787090670582d51ab0a0d37cfeaf4313b053f7b/multidict-6.5.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a6605dc74cd333be279e1fcb568ea24f7bdf1cf09f83a77360ce4dd32d67f14", size = 247951, upload-time = "2025-06-24T22:15:26.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/78/58a9bc0674401f1f26418cd58a5ebf35ce91ead76a22b578908acfe0f4e2/multidict-6.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d64e30ae9ba66ce303a567548a06d64455d97c5dff7052fe428d154274d7174", size = 246786, upload-time = "2025-06-24T22:15:27.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/24/51142ccee295992e22881cccc54b291308423bbcc836fcf4d2edef1a88d0/multidict-6.5.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2fb5dde79a7f6d98ac5e26a4c9de77ccd2c5224a7ce89aeac6d99df7bbe06464", size = 235030, upload-time = "2025-06-24T22:15:29.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/9a/a6f7b75460d3e35b16bf7745c9e3ebb3293324a4295e586563bf50d361f4/multidict-6.5.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8a0d22e8b07cf620e9aeb1582340d00f0031e6a1f3e39d9c2dcbefa8691443b4", size = 253964, upload-time = "2025-06-24T22:15:31.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/f8/0b690674bf8f78604eb0a2b0a85d1380ff3003f270440d40def2a3de8cf4/multidict-6.5.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0120ed5cff2082c7a0ed62a8f80f4f6ac266010c722381816462f279bfa19487", size = 247370, upload-time = "2025-06-24T22:15:33.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/7d/ca55049d1041c517f294c1755c786539cb7a8dc5033361f20ce3a3d817be/multidict-6.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3dea06ba27401c4b54317aa04791182dc9295e7aa623732dd459071a0e0f65db", size = 242920, upload-time = "2025-06-24T22:15:34.669Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/65/f4afa14f0921751864bb3ef80267f15ecae423483e8da9bc5d3757632bfa/multidict-6.5.1-cp313-cp313t-win32.whl", hash = "sha256:93b21be44f3cfee3be68ed5cd8848a3c0420d76dbd12d74f7776bde6b29e5f33", size = 46968, upload-time = "2025-06-24T22:15:36.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/0a/13d08be1ca1523df515fb4efd3cf10f153e62d533f55c53f543cd73041e8/multidict-6.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c5c18f8646a520cc34d00f65f9f6f77782b8a8c59fd8de10713e0de7f470b5d0", size = 52353, upload-time = "2025-06-24T22:15:37.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/dd/84aaf725b236677597a9570d8c1c99af0ba03712149852347969e014d826/multidict-6.5.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eb27128141474a1d545f0531b496c7c2f1c4beff50cb5a828f36eb62fef16c67", size = 44500, upload-time = "2025-06-24T22:15:38.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/9f/d4719ce55a1d8bf6619e8bb92f1e2e7399026ea85ae0c324ec77ee06c050/multidict-6.5.1-py3-none-any.whl", hash = "sha256:895354f4a38f53a1df2cc3fa2223fa714cff2b079a9f018a76cad35e7f0f044c", size = 12185, upload-time = "2025-06-24T22:16:03.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/c9/092c4e9402b6d16de761cff88cb842a5c8cc50ccecaf9c4481ba53264b9e/multidict-6.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:53d92df1752df67a928fa7f884aa51edae6f1cf00eeb38cbcf318cf841c17456", size = 73486, upload-time = "2025-06-17T14:14:37.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/f9/6f7ddb8213f5fdf4db48d1d640b78e8aef89b63a5de8a2313286db709250/multidict-6.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:680210de2c38eef17ce46b8df8bf2c1ece489261a14a6e43c997d49843a27c99", size = 43745, upload-time = "2025-06-17T14:14:38.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/a7/b9be0163bfeee3bb08a77a1705e24eb7e651d594ea554107fac8a1ca6a4d/multidict-6.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e279259bcb936732bfa1a8eec82b5d2352b3df69d2fa90d25808cfc403cee90a", size = 42135, upload-time = "2025-06-17T14:14:39.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/30/93c8203f943a417bda3c573a34d5db0cf733afdfffb0ca78545c7716dbd8/multidict-6.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1c185fc1069781e3fc8b622c4331fb3b433979850392daa5efbb97f7f9959bb", size = 238585, upload-time = "2025-06-17T14:14:41.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/fe/2582b56a1807604774f566eeef183b0d6b148f4b89d1612cd077567b2e1e/multidict-6.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6bb5f65ff91daf19ce97f48f63585e51595539a8a523258b34f7cef2ec7e0617", size = 236174, upload-time = "2025-06-17T14:14:42.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/c4/d8b66d42d385bd4f974cbd1eaa8b265e6b8d297249009f312081d5ded5c7/multidict-6.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8646b4259450c59b9286db280dd57745897897284f6308edbdf437166d93855", size = 250145, upload-time = "2025-06-17T14:14:43.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/64/62feda5093ee852426aae3df86fab079f8bf1cdbe403e1078c94672ad3ec/multidict-6.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d245973d4ecc04eea0a8e5ebec7882cf515480036e1b48e65dffcfbdf86d00be", size = 243470, upload-time = "2025-06-17T14:14:45.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/dc/9f6fa6e854625cf289c0e9f4464b40212a01f76b2f3edfe89b6779b4fb93/multidict-6.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a133e7ddc9bc7fb053733d0ff697ce78c7bf39b5aec4ac12857b6116324c8d75", size = 236968, upload-time = "2025-06-17T14:14:46.609Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/ae/4b81c6e3745faee81a156f3f87402315bdccf04236f75c03e37be19c94ff/multidict-6.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80d696fa38d738fcebfd53eec4d2e3aeb86a67679fd5e53c325756682f152826", size = 236575, upload-time = "2025-06-17T14:14:47.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/fa/4089d7642ea344226e1bfab60dd588761d4791754f8072e911836a39bedf/multidict-6.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:20d30c9410ac3908abbaa52ee5967a754c62142043cf2ba091e39681bd51d21a", size = 247632, upload-time = "2025-06-17T14:14:49.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/ee/a353dac797de0f28fb7f078cc181c5f2eefe8dd16aa11a7100cbdc234037/multidict-6.5.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c65068cc026f217e815fa519d8e959a7188e94ec163ffa029c94ca3ef9d4a73", size = 243520, upload-time = "2025-06-17T14:14:50.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ec/560deb3d2d95822d6eb1bcb1f1cb728f8f0197ec25be7c936d5d6a5d133c/multidict-6.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e355ac668a8c3e49c2ca8daa4c92f0ad5b705d26da3d5af6f7d971e46c096da7", size = 248551, upload-time = "2025-06-17T14:14:52.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/85/ddf277e67c78205f6695f2a7639be459bca9cc353b962fd8085a492a262f/multidict-6.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08db204213d0375a91a381cae0677ab95dd8c67a465eb370549daf6dbbf8ba10", size = 258362, upload-time = "2025-06-17T14:14:53.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/fc/d64ee1df9b87c5210f2d4c419cab07f28589c81b4e5711eda05a122d0614/multidict-6.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ffa58e3e215af8f6536dc837a990e456129857bb6fd546b3991be470abd9597a", size = 253862, upload-time = "2025-06-17T14:14:55.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/7c/a2743c00d9e25f4826d3a77cc13d4746398872cf21c843eef96bb9945665/multidict-6.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e86eb90015c6f21658dbd257bb8e6aa18bdb365b92dd1fba27ec04e58cdc31b", size = 247391, upload-time = "2025-06-17T14:14:57.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/03/7773518db74c442904dbd349074f1e7f2a854cee4d9529fc59e623d3949e/multidict-6.5.0-cp313-cp313-win32.whl", hash = "sha256:f34a90fbd9959d0f857323bd3c52b3e6011ed48f78d7d7b9e04980b8a41da3af", size = 41115, upload-time = "2025-06-17T14:14:59.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/9a/6fc51b1dc11a7baa944bc101a92167d8b0f5929d376a8c65168fc0d35917/multidict-6.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fcb2aa79ac6aef8d5b709bbfc2fdb1d75210ba43038d70fbb595b35af470ce06", size = 44768, upload-time = "2025-06-17T14:15:00.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/2d/0d010be24b663b3c16e3d3307bbba2de5ae8eec496f6027d5c0515b371a8/multidict-6.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:6dcee5e7e92060b4bb9bb6f01efcbb78c13d0e17d9bc6eec71660dd71dc7b0c2", size = 41770, upload-time = "2025-06-17T14:15:01.854Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/d1/a71711a5f32f84b7b036e82182e3250b949a0ce70d51a2c6a4079e665449/multidict-6.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:cbbc88abea2388fde41dd574159dec2cda005cb61aa84950828610cb5010f21a", size = 80450, upload-time = "2025-06-17T14:15:02.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/a2/953a9eede63a98fcec2c1a2c1a0d88de120056219931013b871884f51b43/multidict-6.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70b599f70ae6536e5976364d3c3cf36f40334708bd6cebdd1e2438395d5e7676", size = 46971, upload-time = "2025-06-17T14:15:04.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/61/60250212953459edda2c729e1d85130912f23c67bd4f585546fe4bdb1578/multidict-6.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:828bab777aa8d29d59700018178061854e3a47727e0611cb9bec579d3882de3b", size = 45548, upload-time = "2025-06-17T14:15:05.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/b6/e78ee82e96c495bc2582b303f68bed176b481c8d81a441fec07404fce2ca/multidict-6.5.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9695fc1462f17b131c111cf0856a22ff154b0480f86f539d24b2778571ff94d", size = 238545, upload-time = "2025-06-17T14:15:06.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/0f/6132ca06670c8d7b374c3a4fd1ba896fc37fbb66b0de903f61db7d1020ec/multidict-6.5.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b5ac6ebaf5d9814b15f399337ebc6d3a7f4ce9331edd404e76c49a01620b68d", size = 229931, upload-time = "2025-06-17T14:15:08.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/63/d9957c506e6df6b3e7a194f0eea62955c12875e454b978f18262a65d017b/multidict-6.5.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84a51e3baa77ded07be4766a9e41d977987b97e49884d4c94f6d30ab6acaee14", size = 248181, upload-time = "2025-06-17T14:15:09.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/3f/7d5490579640db5999a948e2c41d4a0efd91a75989bda3e0a03a79c92be2/multidict-6.5.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8de67f79314d24179e9b1869ed15e88d6ba5452a73fc9891ac142e0ee018b5d6", size = 241846, upload-time = "2025-06-17T14:15:11.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/f7/252b1ce949ece52bba4c0de7aa2e3a3d5964e800bce71fb778c2e6c66f7c/multidict-6.5.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17f78a52c214481d30550ec18208e287dfc4736f0c0148208334b105fd9e0887", size = 232893, upload-time = "2025-06-17T14:15:12.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/7e/0070bfd48c16afc26e056f2acce49e853c0d604a69c7124bc0bbdb1bcc0a/multidict-6.5.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2966d0099cb2e2039f9b0e73e7fd5eb9c85805681aa2a7f867f9d95b35356921", size = 228567, upload-time = "2025-06-17T14:15:14.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/31/90551c75322113ebf5fd9c5422e8641d6952f6edaf6b6c07fdc49b1bebdd/multidict-6.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:86fb42ed5ed1971c642cc52acc82491af97567534a8e381a8d50c02169c4e684", size = 246188, upload-time = "2025-06-17T14:15:15.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/e2/aa4b02a55e7767ff292871023817fe4db83668d514dab7ccbce25eaf7659/multidict-6.5.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:4e990cbcb6382f9eae4ec720bcac6a1351509e6fc4a5bb70e4984b27973934e6", size = 235178, upload-time = "2025-06-17T14:15:17.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/5c/f67e726717c4b138b166be1700e2b56e06fbbcb84643d15f9a9d7335ff41/multidict-6.5.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d99a59d64bb1f7f2117bec837d9e534c5aeb5dcedf4c2b16b9753ed28fdc20a3", size = 243422, upload-time = "2025-06-17T14:15:18.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/1c/15fa318285e26a50aa3fa979bbcffb90f9b4d5ec58882d0590eda067d0da/multidict-6.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e8ef15cc97c9890212e1caf90f0d63f6560e1e101cf83aeaf63a57556689fb34", size = 254898, upload-time = "2025-06-17T14:15:20.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/3d/d6c6d1c2e9b61ca80313912d30bb90d4179335405e421ef0a164eac2c0f9/multidict-6.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:b8a09aec921b34bd8b9f842f0bcfd76c6a8c033dc5773511e15f2d517e7e1068", size = 247129, upload-time = "2025-06-17T14:15:21.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/15/1568258cf0090bfa78d44be66247cfdb16e27dfd935c8136a1e8632d3057/multidict-6.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ff07b504c23b67f2044533244c230808a1258b3493aaf3ea2a0785f70b7be461", size = 243841, upload-time = "2025-06-17T14:15:23.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/57/64af5dbcfd61427056e840c8e520b502879d480f9632fbe210929fd87393/multidict-6.5.0-cp313-cp313t-win32.whl", hash = "sha256:9232a117341e7e979d210e41c04e18f1dc3a1d251268df6c818f5334301274e1", size = 46761, upload-time = "2025-06-17T14:15:24.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/a8/cac7f7d61e188ff44f28e46cb98f9cc21762e671c96e031f06c84a60556e/multidict-6.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:44cb5c53fb2d4cbcee70a768d796052b75d89b827643788a75ea68189f0980a1", size = 52112, upload-time = "2025-06-17T14:15:25.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/9f/076533feb1b5488d22936da98b9c217205cfbf9f56f7174e8c5c86d86fe6/multidict-6.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:51d33fafa82640c0217391d4ce895d32b7e84a832b8aee0dcc1b04d8981ec7f4", size = 44358, upload-time = "2025-06-17T14:15:27.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/d8/45e8fc9892a7386d074941429e033adb4640e59ff0780d96a8cf46fe788e/multidict-6.5.0-py3-none-any.whl", hash = "sha256:5634b35f225977605385f56153bd95a7133faffc0ffe12ad26e10517537e8dfc", size = 12181, upload-time = "2025-06-17T14:15:55.156Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2148,11 +2151,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.3.1"
|
||||
version = "3.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/8a/6ea75ff7acf89f43afb157604429af4661a9840b1f2cece602b6a13c1893/oauthlib-3.3.0.tar.gz", hash = "sha256:4e707cf88d7dfc22a8cce22ca736a2eef9967c1dd3845efc0703fc922353eeb2", size = 190292, upload-time = "2025-06-17T23:19:18.309Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/3d/760b1456010ed11ce87c0109007f0166078dfdada7597f0091ae76eb7305/oauthlib-3.3.0-py3-none-any.whl", hash = "sha256:a2b3a0a2a4ec2feb4b9110f56674a39b2cc2f23e14713f4ed20441dfba14e934", size = 165155, upload-time = "2025-06-17T23:19:16.771Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2547,11 +2550,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
version = "2.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2708,11 +2711,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.1"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2961,15 +2964,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.31.0"
|
||||
version = "2.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/45/c7ef7e12d8434fda8b61cdab432d8af64fb832480c93cdaf4bdcab7f5597/sentry_sdk-2.31.0.tar.gz", hash = "sha256:fed6d847f15105849cdf5dfdc64dcec356f936d41abb8c9d66adae45e60959ec", size = 334167, upload-time = "2025-06-24T16:36:26.066Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/4c/af31e0201b48469786ddeb1bf6fd3dfa3a291cc613a0fe6a60163a7535f9/sentry_sdk-2.30.0.tar.gz", hash = "sha256:436369b02afef7430efb10300a344fb61a11fe6db41c2b11f41ee037d2dd7f45", size = 326767, upload-time = "2025-06-12T10:34:34.733Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a2/9b6d8cc59f03251c583b3fec9d2f075dc09c0f6e030e0e0a3b223c6e64b2/sentry_sdk-2.31.0-py2.py3-none-any.whl", hash = "sha256:e953f5ab083e6599bab255b75d6829b33b3ddf9931a27ca00b4ab0081287e84f", size = 355638, upload-time = "2025-06-24T16:36:24.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/99/31ac6faaae33ea698086692638f58d14f121162a8db0039e68e94135e7f1/sentry_sdk-2.30.0-py2.py3-none-any.whl", hash = "sha256:59391db1550662f746ea09b483806a631c3ae38d6340804a1a4c0605044f6877", size = 343149, upload-time = "2025-06-12T10:34:32.896Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
504
web/package-lock.json
generated
504
web/package-lock.json
generated
@ -22,7 +22,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.7.11",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@goauthentik/api": "^2025.6.2-1750856752",
|
||||
"@goauthentik/api": "^2025.6.2-1750246811",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
@ -34,7 +34,7 @@
|
||||
"@openlayers-elements/maps": "^0.4.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.32.0",
|
||||
"@sentry/browser": "^9.30.0",
|
||||
"@spotlightjs/spotlight": "^3.0.1",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
@ -75,7 +75,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.27.0",
|
||||
"@goauthentik/core": "^1.0.0",
|
||||
"@goauthentik/esbuild-plugin-live-reload": "^1.0.5",
|
||||
"@goauthentik/esbuild-plugin-live-reload": "^1.0.4",
|
||||
"@goauthentik/eslint-config": "^1.0.5",
|
||||
"@goauthentik/prettier-config": "^1.0.5",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
@ -126,7 +126,7 @@
|
||||
"storybook-addon-mock": "^5.0.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.35.0",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
"vite-plugin-lit-css": "^2.0.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wireit": "^0.14.12"
|
||||
@ -1716,30 +1716,32 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@gerrit0/mini-shiki": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.4.2.tgz",
|
||||
"integrity": "sha512-3jXo5bNjvvimvdbIhKGfFxSnKCX+MA8wzHv55ptzk/cx8wOzT+BRcYgj8aFN3yTiTs+zvQQiaZFr7Jce1ZG3fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/engine-oniguruma": "^3.4.2",
|
||||
"@shikijs/langs": "^3.4.2",
|
||||
"@shikijs/themes": "^3.4.2",
|
||||
"@shikijs/types": "^3.4.2",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2025.6.2-1750856752",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750856752.tgz",
|
||||
"integrity": "sha512-Zf/1wa5Q1CBbfc4EyJYc/JieTnMS9V0k4wGlK3ojC+kTDJhGjYdHPWpOGiAV9GJXQWHXfHLpA9bqPtBx/0ww7A=="
|
||||
"version": "2025.6.2-1750246811",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750246811.tgz",
|
||||
"integrity": "sha512-ENHEi3kGAodf5tKQb5kziUrT1EcJw3z8tp2mU7LWqNlXr4eoAI15BjDfH5DW56l4jy3xKqTd+R2Ntnj4hiVhHw=="
|
||||
},
|
||||
"node_modules/@goauthentik/core": {
|
||||
"resolved": "packages/core",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@goauthentik/esbuild-plugin-live-reload": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/esbuild-plugin-live-reload/-/esbuild-plugin-live-reload-1.0.5.tgz",
|
||||
"integrity": "sha512-MZ/najY+Xn62ijzj7JDS1sVupWI3BNRwJc4kykB/iP9CdLJw+xO71qPTjfCEEOVYMZrOTftD4KOLhRYx3GTqkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-free-ports": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild": "^0.25.4"
|
||||
}
|
||||
"resolved": "packages/esbuild-plugin-live-reload",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@goauthentik/eslint-config": {
|
||||
"version": "1.0.5",
|
||||
@ -4056,7 +4058,6 @@
|
||||
"integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
@ -4560,75 +4561,75 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "9.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.32.0.tgz",
|
||||
"integrity": "sha512-mVWdruSWXF+2WgS24jwLhWFyC/nDQbKXseLR8paU9LGSnVtlBlQseIx1GrANbJrhBxiEWSft4WiuxU34wPsbXg==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.30.0.tgz",
|
||||
"integrity": "sha512-e6ZlN8oWheCB0YJSGlBNUlh6UPnY5Ecj1P+/cgeKBhNm7c3bIx4J50485hB8LQsu+b7Q11L2o/wucZ//Pb6FCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.32.0"
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "9.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.32.0.tgz",
|
||||
"integrity": "sha512-OaXaovXqlhN1sG2wtJMhxMEjyeuK7RwY57o96LgKE0bWM//Fs9WWCOkGa+7l8TOf0+0ib7gfhJZlpN0hlqOgRw==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.30.0.tgz",
|
||||
"integrity": "sha512-qAZ7xxLqZM7GlEvmSUmTHnoueg+fc7esMQD4vH8pS7HI3n9C5MjGn3HHlndRpD8lL7iUUQ0TPZQgU6McbzMDyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.32.0"
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "9.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.32.0.tgz",
|
||||
"integrity": "sha512-mOHUKjUtHbEwshikrCQPM1ZqWAMUEcpEGashnXQp3KQivvbTxrExiNnt6XK5TjJyGvsI3A907Bp/HvEzgneYgQ==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.30.0.tgz",
|
||||
"integrity": "sha512-+6wkqQGLJuFUzvGRzbh3iIhFGyxQx/Oxc0ODDKmz9ag2xYRjCYb3UUQXmQX9navAF0HXUsq8ajoJPm2L1ZyWVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.32.0",
|
||||
"@sentry/core": "9.32.0"
|
||||
"@sentry-internal/browser-utils": "9.30.0",
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "9.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.32.0.tgz",
|
||||
"integrity": "sha512-tu+coeTRpJxknmWPMJC2jqmIM5IsVoRn9gEDdkSrcPbgx/GwgE03fSJVBJL1tOEA8yRNIhZPMR86ORE7/7n2ow==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.30.0.tgz",
|
||||
"integrity": "sha512-I4MxS27rfV7vnOU29L80y4baZ4I1XqpnYvC/yLN7C17nA8eDCufQ8WVomli41y8JETnfcxlm68z7CS0sO4RCSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "9.32.0",
|
||||
"@sentry/core": "9.32.0"
|
||||
"@sentry-internal/replay": "9.30.0",
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "9.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.32.0.tgz",
|
||||
"integrity": "sha512-BzPogpH87n+sC9VPfXaXkiKJtagLpIB87LGg1hSBURpwGx6Rt2ORmaVYgwwuuFZX8Hia727IIM7pbcbNfrXGRQ==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.30.0.tgz",
|
||||
"integrity": "sha512-sRyW6A9nIieTTI26MYXk1DmWEhmphTjZevusNWla+vvUigCmSjuH+xZw19w43OyvF3bu261Skypnm/mAalOTwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.32.0",
|
||||
"@sentry-internal/feedback": "9.32.0",
|
||||
"@sentry-internal/replay": "9.32.0",
|
||||
"@sentry-internal/replay-canvas": "9.32.0",
|
||||
"@sentry/core": "9.32.0"
|
||||
"@sentry-internal/browser-utils": "9.30.0",
|
||||
"@sentry-internal/feedback": "9.30.0",
|
||||
"@sentry-internal/replay": "9.30.0",
|
||||
"@sentry-internal/replay-canvas": "9.30.0",
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "9.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.32.0.tgz",
|
||||
"integrity": "sha512-1wAXMMmeY4Ny2MJBCuri3b4LMVPjqXdgbVgTxxipGW+gzPsjv+8+LCSnJAR/cRBr8JoXV+qGC2tE06rI1XDj3A==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.30.0.tgz",
|
||||
"integrity": "sha512-JfEpeQ8a1qVJEb9DxpFTFy1J1gkNdlgKAPiqYGNnm4yQbnfl2Kb/iEo1if70FkiHc52H8fJwISEF90pzMm6lPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@ -4718,6 +4719,55 @@
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-oniguruma": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.4.2.tgz",
|
||||
"integrity": "sha512-zcZKMnNndgRa3ORja6Iemsr3DrLtkX3cAF7lTJkdMB6v9alhlBsX9uNiCpqofNrXOvpA3h6lHcLJxgCIhVOU5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.4.2",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/langs": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.4.2.tgz",
|
||||
"integrity": "sha512-H6azIAM+OXD98yztIfs/KH5H4PU39t+SREhmM8LaNXyUrqj2mx+zVkr8MWYqjceSjDw9I1jawm1WdFqU806rMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/themes": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.4.2.tgz",
|
||||
"integrity": "sha512-qAEuAQh+brd8Jyej2UDDf+b4V2g1Rm8aBIdvt32XhDPrHvDkEnpb7Kzc9hSuHUxz0Iuflmq7elaDuQAP9bHIhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/types": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.4.2.tgz",
|
||||
"integrity": "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/vscode-textmate": {
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
|
||||
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
@ -7365,17 +7415,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
|
||||
"integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/type-utils": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/type-utils": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@ -7389,7 +7439,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.35.0",
|
||||
"@typescript-eslint/parser": "^8.34.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
@ -7405,16 +7455,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
|
||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -7430,14 +7480,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
|
||||
"integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
||||
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.35.0",
|
||||
"@typescript-eslint/types": "^8.35.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
||||
"@typescript-eslint/types": "^8.34.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -7452,14 +7502,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
|
||||
"integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
||||
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0"
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -7470,9 +7520,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
|
||||
"integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -7487,14 +7537,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
|
||||
"integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@ -7511,9 +7561,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
|
||||
"integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
||||
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -7525,16 +7575,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
|
||||
"integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
||||
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.35.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"@typescript-eslint/project-service": "8.34.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -7554,16 +7604,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
|
||||
"integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
||||
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.35.0"
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -7578,13 +7628,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
|
||||
"integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
||||
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -10330,20 +10380,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
|
||||
"integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
|
||||
"integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.24.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"is-string": "^1.1.1",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
"es-abstract": "^1.23.2",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"is-string": "^1.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -13188,7 +13236,6 @@
|
||||
"integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
}
|
||||
@ -13199,7 +13246,6 @@
|
||||
"integrity": "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
@ -13596,9 +13642,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.24.0",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
|
||||
"integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
|
||||
"version": "1.23.9",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
||||
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -13606,18 +13652,18 @@
|
||||
"arraybuffer.prototype.slice": "^1.0.4",
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"call-bound": "^1.0.3",
|
||||
"data-view-buffer": "^1.0.2",
|
||||
"data-view-byte-length": "^1.0.2",
|
||||
"data-view-byte-offset": "^1.0.1",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"es-to-primitive": "^1.3.0",
|
||||
"function.prototype.name": "^1.1.8",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"get-proto": "^1.0.1",
|
||||
"get-intrinsic": "^1.2.7",
|
||||
"get-proto": "^1.0.0",
|
||||
"get-symbol-description": "^1.1.0",
|
||||
"globalthis": "^1.0.4",
|
||||
"gopd": "^1.2.0",
|
||||
@ -13629,24 +13675,21 @@
|
||||
"is-array-buffer": "^3.0.5",
|
||||
"is-callable": "^1.2.7",
|
||||
"is-data-view": "^1.0.2",
|
||||
"is-negative-zero": "^2.0.3",
|
||||
"is-regex": "^1.2.1",
|
||||
"is-set": "^2.0.3",
|
||||
"is-shared-array-buffer": "^1.0.4",
|
||||
"is-string": "^1.1.1",
|
||||
"is-typed-array": "^1.1.15",
|
||||
"is-weakref": "^1.1.1",
|
||||
"is-weakref": "^1.1.0",
|
||||
"math-intrinsics": "^1.1.0",
|
||||
"object-inspect": "^1.13.4",
|
||||
"object-inspect": "^1.13.3",
|
||||
"object-keys": "^1.1.1",
|
||||
"object.assign": "^4.1.7",
|
||||
"own-keys": "^1.0.1",
|
||||
"regexp.prototype.flags": "^1.5.4",
|
||||
"regexp.prototype.flags": "^1.5.3",
|
||||
"safe-array-concat": "^1.1.3",
|
||||
"safe-push-apply": "^1.0.0",
|
||||
"safe-regex-test": "^1.1.0",
|
||||
"set-proto": "^1.0.0",
|
||||
"stop-iteration-iterator": "^1.1.0",
|
||||
"string.prototype.trim": "^1.2.10",
|
||||
"string.prototype.trimend": "^1.0.9",
|
||||
"string.prototype.trimstart": "^1.0.8",
|
||||
@ -13655,7 +13698,7 @@
|
||||
"typed-array-byte-offset": "^1.0.4",
|
||||
"typed-array-length": "^1.0.7",
|
||||
"unbox-primitive": "^1.1.0",
|
||||
"which-typed-array": "^1.1.19"
|
||||
"which-typed-array": "^1.1.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -14580,9 +14623,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-module-utils": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
|
||||
"integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
|
||||
"integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -14608,30 +14651,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import": {
|
||||
"version": "2.32.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"version": "2.31.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
|
||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
"array.prototype.findlastindex": "^1.2.6",
|
||||
"array.prototype.flat": "^1.3.3",
|
||||
"array.prototype.flatmap": "^1.3.3",
|
||||
"array-includes": "^3.1.8",
|
||||
"array.prototype.findlastindex": "^1.2.5",
|
||||
"array.prototype.flat": "^1.3.2",
|
||||
"array.prototype.flatmap": "^1.3.2",
|
||||
"debug": "^3.2.7",
|
||||
"doctrine": "^2.1.0",
|
||||
"eslint-import-resolver-node": "^0.3.9",
|
||||
"eslint-module-utils": "^2.12.1",
|
||||
"eslint-module-utils": "^2.12.0",
|
||||
"hasown": "^2.0.2",
|
||||
"is-core-module": "^2.16.1",
|
||||
"is-core-module": "^2.15.1",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^3.1.2",
|
||||
"object.fromentries": "^2.0.8",
|
||||
"object.groupby": "^1.0.3",
|
||||
"object.values": "^1.2.1",
|
||||
"object.values": "^1.2.0",
|
||||
"semver": "^6.3.1",
|
||||
"string.prototype.trimend": "^1.0.9",
|
||||
"string.prototype.trimend": "^1.0.8",
|
||||
"tsconfig-paths": "^3.15.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -15650,7 +15693,6 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/find-free-ports/-/find-free-ports-3.1.1.tgz",
|
||||
"integrity": "sha512-hQebewth9i5qkf0a0u06iFaxQssk5ZnPBBggsa1vk8zCYaZoz9IZXpoRLTbEOrYdqfrjvcxU00gYoCPgmXugKA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-replace": {
|
||||
@ -16195,7 +16237,6 @@
|
||||
"integrity": "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/fisker/git-hooks-list?sponsor=1"
|
||||
}
|
||||
@ -17341,10 +17382,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"license": "MIT",
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
@ -17523,19 +17563,6 @@
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-negative-zero": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
|
||||
"integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
@ -19126,6 +19153,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz",
|
||||
@ -19530,6 +19567,13 @@
|
||||
"node": ">=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
@ -19591,6 +19635,24 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-table": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
||||
@ -19988,6 +20050,13 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@ -22930,7 +22999,6 @@
|
||||
"integrity": "sha512-h+3tSpr2nVpp+YOK1MDIYtYhHVXr8/0V59UUbJpIJFaqi3w4fvUokJo6eV8W+vELrUXIZzJ+DKm5G7lYzrMcKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"sort-package-json": "3.2.1",
|
||||
"synckit": "0.11.6"
|
||||
@ -23178,6 +23246,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer-core": {
|
||||
"version": "22.15.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz",
|
||||
@ -23943,17 +24021,14 @@
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
|
||||
"integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"set-function-name": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@ -25280,8 +25355,7 @@
|
||||
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz",
|
||||
"integrity": "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sort-package-json": {
|
||||
"version": "3.2.1",
|
||||
@ -25289,7 +25363,6 @@
|
||||
"integrity": "sha512-rTfRdb20vuoAn7LDlEtCqOkYfl2X+Qze6cLbNOzcDpbmKEhJI30tTN44d5shbKJnXsvz24QQhlCm81Bag7EOKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-indent": "^7.0.1",
|
||||
"detect-newline": "^4.0.1",
|
||||
@ -25457,20 +25530,6 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/stop-iteration-iterator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
||||
"integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"internal-slot": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/storybook": {
|
||||
"version": "8.6.14",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.14.tgz",
|
||||
@ -25982,7 +26041,6 @@
|
||||
"integrity": "sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.4"
|
||||
},
|
||||
@ -26193,7 +26251,6 @@
|
||||
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
@ -27063,6 +27120,43 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.28.5",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.5.tgz",
|
||||
"integrity": "sha512-5PzUddaA9FbaarUzIsEc4wNXCiO4Ot3bJNeMF2qKpYlTmM9TTaSHQ7162w756ERCkXER/+o2purRG6YOAv6EMA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@gerrit0/mini-shiki": "^3.2.2",
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it": "^14.1.0",
|
||||
"minimatch": "^9.0.5",
|
||||
"yaml": "^2.7.1"
|
||||
},
|
||||
"bin": {
|
||||
"typedoc": "bin/typedoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18",
|
||||
"pnpm": ">= 10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc-plugin-markdown": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.6.3.tgz",
|
||||
"integrity": "sha512-86oODyM2zajXwLs4Wok2mwVEfCwCnp756QyhLGX2IfsdRYr1DXLCgJgnLndaMUjJD7FBhnLk2okbNE9PdLxYRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typedoc": "0.28.x"
|
||||
}
|
||||
},
|
||||
"node_modules/types-ramda": {
|
||||
"version": "0.30.1",
|
||||
"resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz",
|
||||
@ -27087,15 +27181,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
|
||||
"integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
||||
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.35.0",
|
||||
"@typescript-eslint/parser": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||
"@typescript-eslint/parser": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -27119,6 +27213,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
|
||||
@ -29330,7 +29431,6 @@
|
||||
"packages/esbuild-plugin-live-reload": {
|
||||
"name": "@goauthentik/esbuild-plugin-live-reload",
|
||||
"version": "1.0.5",
|
||||
"extraneous": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-free-ports": "^3.1.1"
|
||||
@ -29354,6 +29454,16 @@
|
||||
"esbuild": "^0.25.5"
|
||||
}
|
||||
},
|
||||
"packages/esbuild-plugin-live-reload/node_modules/@types/node": {
|
||||
"version": "22.15.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.19.tgz",
|
||||
"integrity": "sha512-3vMNr4TzNQyjHcRZadojpRaD9Ofr6LsonZAoQ+HMUa/9ORTPoxVIw0e0mpqWpdjj8xybyCM+oKOUH2vwFu/oEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"packages/monorepo": {
|
||||
"name": "@goauthentik/monorepo",
|
||||
"version": "1.0.0",
|
||||
|
@ -93,7 +93,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.7.11",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@goauthentik/api": "^2025.6.2-1750856752",
|
||||
"@goauthentik/api": "^2025.6.2-1750246811",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
@ -105,7 +105,7 @@
|
||||
"@openlayers-elements/maps": "^0.4.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.32.0",
|
||||
"@sentry/browser": "^9.30.0",
|
||||
"@spotlightjs/spotlight": "^3.0.1",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
@ -146,7 +146,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.27.0",
|
||||
"@goauthentik/core": "^1.0.0",
|
||||
"@goauthentik/esbuild-plugin-live-reload": "^1.0.5",
|
||||
"@goauthentik/esbuild-plugin-live-reload": "^1.0.4",
|
||||
"@goauthentik/eslint-config": "^1.0.5",
|
||||
"@goauthentik/prettier-config": "^1.0.5",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
@ -197,7 +197,7 @@
|
||||
"storybook-addon-mock": "^5.0.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.35.0",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
"vite-plugin-lit-css": "^2.0.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wireit": "^0.14.12"
|
||||
|
@ -1,4 +1,3 @@
|
||||
README.md
|
||||
node_modules
|
||||
_media
|
||||
!.github/README.md
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/esbuild-plugin-live-reload",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/esbuild-plugin-live-reload",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-free-ports": "^3.1.1"
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/esbuild-plugin-live-reload",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"description": "ESBuild + browser refresh. Build completes, page reloads.",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user