Compare commits
51 Commits
docusaurus
...
core/fix-g
Author | SHA1 | Date | |
---|---|---|---|
af9ce90b8b | |||
05e5c6309c | |||
ad4a765a80 | |||
4dcd481010 | |||
d0dc14d84d | |||
7bf960352b | |||
c07d01661b | |||
427597ec14 | |||
7cc77bd387 | |||
381a1a2c49 | |||
08f8222224 | |||
1211c34a18 | |||
22efb57369 | |||
3eeda53be6 | |||
82ace18703 | |||
8589079252 | |||
ae2af6e58e | |||
86a7f98ff6 | |||
3af45371d3 | |||
b01ffd934f | |||
f11ba94603 | |||
7d2aa43364 | |||
f1351a7577 | |||
0611eea0e7 | |||
d0b46fcf9c | |||
dcbdc37d31 | |||
d07f396379 | |||
0972103b83 | |||
b448e76db4 | |||
f2937bd6dd | |||
53c2e3e77c | |||
7dd62c1f55 | |||
33e3510fba | |||
0e5fac2642 | |||
c53b1fe78a | |||
838a7457b2 | |||
a3c07bc9ff | |||
121f2c609d | |||
365affc28e | |||
f367822779 | |||
848198125d | |||
497ac5e3d0 | |||
1773d4d681 | |||
4edbb51939 | |||
c7e97ab48e | |||
31f7faae1c | |||
f5dae2ae92 | |||
2c043dba0b | |||
bda10e5db1 | |||
be9ae7d4f7 | |||
b4a6189bfa |
3
.github/workflows/ci-main.yml
vendored
3
.github/workflows/ci-main.yml
vendored
@ -200,7 +200,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: web/dist
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
|
||||
- name: prepare web ui
|
||||
if: steps.cache-web.outputs.cache-hit != 'true'
|
||||
working-directory: web
|
||||
@ -208,6 +208,7 @@ jobs:
|
||||
npm ci
|
||||
make -C .. gen-client-ts
|
||||
npm run build
|
||||
npm run build:sfe
|
||||
- name: run e2e
|
||||
run: |
|
||||
uv run coverage run manage.py test ${{ matrix.job.glob }}
|
||||
|
@ -40,7 +40,8 @@ COPY ./web /work/web/
|
||||
COPY ./website /work/website/
|
||||
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
||||
|
||||
RUN npm run build
|
||||
RUN npm run build && \
|
||||
npm run build:sfe
|
||||
|
||||
# Stage 3: Build go proxy
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
||||
@ -93,7 +94,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 5: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.2 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.3 AS uv
|
||||
# Stage 6: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
|
||||
|
||||
|
@ -42,4 +42,4 @@ See [SECURITY.md](SECURITY.md)
|
||||
|
||||
## Adoption and Contributions
|
||||
|
||||
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [CONTRIBUTING.md file](./CONTRIBUTING.md).
|
||||
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [contribution guide](https://docs.goauthentik.io/docs/developer-docs?utm_source=github).
|
||||
|
@ -5,10 +5,10 @@ from typing import Any
|
||||
from django.db.models import F, Q
|
||||
from django.db.models import Value as V
|
||||
from django.http.request import HttpRequest
|
||||
from sentry_sdk import get_current_span
|
||||
|
||||
from authentik import get_full_version
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.lib.sentry import get_http_meta
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
_q_default = Q(default=True)
|
||||
@ -32,13 +32,9 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
|
||||
"""Context Processor that injects brand object into every template"""
|
||||
brand = getattr(request, "brand", DEFAULT_BRAND)
|
||||
tenant = getattr(request, "tenant", Tenant())
|
||||
trace = ""
|
||||
span = get_current_span()
|
||||
if span:
|
||||
trace = span.to_traceparent()
|
||||
return {
|
||||
"brand": brand,
|
||||
"footer_links": tenant.footer_links,
|
||||
"sentry_trace": trace,
|
||||
"html_meta": {**get_http_meta()},
|
||||
"version": get_full_version(),
|
||||
}
|
||||
|
@ -16,10 +16,12 @@ from drf_spectacular.utils import (
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, IntegerField, SerializerMethodField
|
||||
from rest_framework.permissions import SAFE_METHODS, BasePermission
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ListSerializer, ValidationError
|
||||
from rest_framework.validators import UniqueValidator
|
||||
from rest_framework.views import View
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
@ -85,34 +87,6 @@ class GroupSerializer(ModelSerializer):
|
||||
raise ValidationError(_("Cannot set group as parent of itself."))
|
||||
return parent
|
||||
|
||||
def validate_is_superuser(self, superuser: bool):
|
||||
"""Ensure that the user creating this group has permissions to set the superuser flag"""
|
||||
request: Request = self.context.get("request", None)
|
||||
if not request:
|
||||
return superuser
|
||||
# If we're updating an instance, and the state hasn't changed, we don't need to check perms
|
||||
if self.instance and superuser == self.instance.is_superuser:
|
||||
return superuser
|
||||
user: User = request.user
|
||||
perm = (
|
||||
"authentik_core.enable_group_superuser"
|
||||
if superuser
|
||||
else "authentik_core.disable_group_superuser"
|
||||
)
|
||||
has_perm = user.has_perm(perm)
|
||||
if self.instance and not has_perm:
|
||||
has_perm = user.has_perm(perm, self.instance)
|
||||
if not has_perm:
|
||||
raise ValidationError(
|
||||
_(
|
||||
(
|
||||
"User does not have permission to set "
|
||||
"superuser status to {superuser_status}."
|
||||
).format_map({"superuser_status": superuser})
|
||||
)
|
||||
)
|
||||
return superuser
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = [
|
||||
@ -180,6 +154,36 @@ class GroupFilter(FilterSet):
|
||||
fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"]
|
||||
|
||||
|
||||
class SuperuserSetter(BasePermission):
|
||||
"""Check for enable_group_superuser or disable_group_superuser permissions"""
|
||||
|
||||
message = _("User does not have permission to set the given superuser status.")
|
||||
enable_perm = "authentik_core.enable_group_superuser"
|
||||
disable_perm = "authentik_core.disable_group_superuser"
|
||||
|
||||
def has_permission(self, request: Request, view: View):
|
||||
if request.method != "POST":
|
||||
return True
|
||||
|
||||
is_superuser = request.data.get("is_superuser", False)
|
||||
if not is_superuser:
|
||||
return True
|
||||
|
||||
return request.user.has_perm(self.enable_perm)
|
||||
|
||||
def has_object_permission(self, request: Request, view: View, object: Group):
|
||||
if request.method in SAFE_METHODS:
|
||||
return True
|
||||
|
||||
new_value = request.data.get("is_superuser")
|
||||
old_value = object.is_superuser
|
||||
if new_value is None or new_value == old_value:
|
||||
return True
|
||||
|
||||
perm = self.enable_perm if new_value else self.disable_perm
|
||||
return request.user.has_perm(perm) or request.user.has_perm(perm, object)
|
||||
|
||||
|
||||
class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
"""Group Viewset"""
|
||||
|
||||
@ -192,6 +196,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
serializer_class = GroupSerializer
|
||||
search_fields = ["name", "is_superuser"]
|
||||
filterset_class = GroupFilter
|
||||
permission_classes = [SuperuserSetter]
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.management import create_permissions
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand, no_translations
|
||||
from guardian.management import create_anonymous_user
|
||||
|
||||
@ -16,6 +17,10 @@ class Command(BaseCommand):
|
||||
"""Check permissions for all apps"""
|
||||
for tenant in Tenant.objects.filter(ready=True):
|
||||
with tenant:
|
||||
# See https://code.djangoproject.com/ticket/28417
|
||||
# Remove potential lingering old permissions
|
||||
call_command("remove_stale_contenttypes", "--no-input")
|
||||
|
||||
for app in apps.get_app_configs():
|
||||
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
|
||||
create_permissions(app, verbosity=0)
|
||||
|
@ -31,7 +31,10 @@ class PickleSerializer:
|
||||
|
||||
def loads(self, data):
|
||||
"""Unpickle data to be loaded from redis"""
|
||||
return pickle.loads(data) # nosec
|
||||
try:
|
||||
return pickle.loads(data) # nosec
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _migrate_session(
|
||||
|
@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-14 11:15
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def remove_old_authenticated_session_content_type(
|
||||
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
|
||||
):
|
||||
db_alias = schema_editor.connection.alias
|
||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||
|
||||
ContentType.objects.using(db_alias).filter(model="oldauthenticatedsession").delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0047_delete_oldauthenticatedsession"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=remove_old_authenticated_session_content_type,
|
||||
),
|
||||
]
|
@ -21,7 +21,9 @@
|
||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||
{% for key, value in html_meta.items %}
|
||||
<meta name="{{key}}" content="{{ value }}" />
|
||||
{% endfor %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
|
@ -118,12 +118,25 @@ class TestGroupsAPI(APITestCase):
|
||||
reverse("authentik_api:group-list"),
|
||||
data={"name": generate_id(), "is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertEqual(res.status_code, 403)
|
||||
self.assertJSONEqual(
|
||||
res.content,
|
||||
{"is_superuser": ["User does not have permission to set superuser status to True."]},
|
||||
{"detail": "User does not have permission to set the given superuser status."},
|
||||
)
|
||||
|
||||
def test_superuser_update_object_perm(self):
|
||||
"""Test updating a superuser group with object permission"""
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=False)
|
||||
assign_perm("view_group", self.login_user, group)
|
||||
assign_perm("change_group", self.login_user, group)
|
||||
assign_perm("enable_group_superuser", self.login_user, group)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_superuser_update_no_perm(self):
|
||||
"""Test updating a superuser group without permission"""
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
||||
@ -134,10 +147,10 @@ class TestGroupsAPI(APITestCase):
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"is_superuser": False},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertEqual(res.status_code, 403)
|
||||
self.assertJSONEqual(
|
||||
res.content,
|
||||
{"is_superuser": ["User does not have permission to set superuser status to False."]},
|
||||
{"detail": "User does not have permission to set the given superuser status."},
|
||||
)
|
||||
|
||||
def test_superuser_update_no_change(self):
|
||||
@ -163,3 +176,27 @@ class TestGroupsAPI(APITestCase):
|
||||
data={"name": generate_id(), "is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
||||
def test_superuser_create_no_perm(self):
|
||||
"""Test creating a superuser group with no permission"""
|
||||
assign_perm("authentik_core.add_group", self.login_user)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:group-list"),
|
||||
data={"name": generate_id(), "is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 403)
|
||||
self.assertJSONEqual(
|
||||
res.content,
|
||||
{"detail": "User does not have permission to set the given superuser status."},
|
||||
)
|
||||
|
||||
def test_no_superuser_create_no_perm(self):
|
||||
"""Test creating a non-superuser group with no permission"""
|
||||
assign_perm("authentik_core.add_group", self.login_user)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:group-list"),
|
||||
data={"name": generate_id()},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
@ -132,13 +132,14 @@ class LicenseKey:
|
||||
"""Get a summarized version of all (not expired) licenses"""
|
||||
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
||||
for lic in License.objects.all():
|
||||
total.internal_users += lic.internal_users
|
||||
total.external_users += lic.external_users
|
||||
if lic.is_valid:
|
||||
total.internal_users += lic.internal_users
|
||||
total.external_users += lic.external_users
|
||||
total.license_flags.extend(lic.status.license_flags)
|
||||
exp_ts = int(mktime(lic.expiry.timetuple()))
|
||||
if total.exp == 0:
|
||||
total.exp = exp_ts
|
||||
total.exp = max(total.exp, exp_ts)
|
||||
total.license_flags.extend(lic.status.license_flags)
|
||||
return total
|
||||
|
||||
@staticmethod
|
||||
|
@ -39,6 +39,10 @@ class License(SerializerModel):
|
||||
internal_users = models.BigIntegerField()
|
||||
external_users = models.BigIntegerField()
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return self.expiry >= now()
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[BaseSerializer]:
|
||||
from authentik.enterprise.api import LicenseSerializer
|
||||
|
@ -8,6 +8,7 @@ from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
from authentik.enterprise.models import (
|
||||
THRESHOLD_READ_ONLY_WEEKS,
|
||||
@ -71,9 +72,9 @@ class TestEnterpriseLicense(TestCase):
|
||||
)
|
||||
def test_valid_multiple(self):
|
||||
"""Check license verification"""
|
||||
lic = License.objects.create(key=generate_id())
|
||||
lic = License.objects.create(key=generate_id(), expiry=expiry_valid)
|
||||
self.assertTrue(lic.status.status().is_valid)
|
||||
lic2 = License.objects.create(key=generate_id())
|
||||
lic2 = License.objects.create(key=generate_id(), expiry=expiry_valid)
|
||||
self.assertTrue(lic2.status.status().is_valid)
|
||||
total = LicenseKey.get_total()
|
||||
self.assertEqual(total.internal_users, 200)
|
||||
@ -232,7 +233,9 @@ class TestEnterpriseLicense(TestCase):
|
||||
)
|
||||
def test_expiry_expired(self):
|
||||
"""Check license verification"""
|
||||
License.objects.create(key=generate_id())
|
||||
User.objects.all().delete()
|
||||
License.objects.all().delete()
|
||||
License.objects.create(key=generate_id(), expiry=expiry_expired)
|
||||
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRED)
|
||||
|
||||
@patch(
|
||||
|
@ -15,6 +15,7 @@
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||
{% include "base/header_js.html" %}
|
||||
<style>
|
||||
html,
|
||||
@ -22,7 +23,7 @@
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-image: url("{{ flow.background_url }}");
|
||||
background-image: url("{{ flow_background_url }}");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
{% block head_before %}
|
||||
{{ block.super }}
|
||||
<link rel="prefetch" href="{{ flow.background_url }}" />
|
||||
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||
{% if flow.compatibility_mode and not inspector %}
|
||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
||||
{% endif %}
|
||||
@ -21,7 +21,7 @@ window.authentik.flow = {
|
||||
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
||||
<style>
|
||||
:root {
|
||||
--ak-flow-background: url("{{ flow.background_url }}");
|
||||
--ak-flow-background: url("{{ flow_background_url }}");
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
@ -13,7 +13,9 @@ class FlowInterfaceView(InterfaceView):
|
||||
"""Flow interface"""
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||
flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||
kwargs["flow"] = flow
|
||||
kwargs["flow_background_url"] = flow.background_url(self.request)
|
||||
kwargs["inspector"] = "inspector" in self.request.GET
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
@ -363,6 +363,9 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
|
||||
pool_options = config.get_dict_from_b64_json("postgresql.pool_options", True)
|
||||
if not pool_options:
|
||||
pool_options = True
|
||||
# FIXME: Temporarily force pool to be deactivated.
|
||||
# See https://github.com/goauthentik/authentik/issues/14320
|
||||
pool_options = False
|
||||
|
||||
db = {
|
||||
"default": {
|
||||
|
@ -17,7 +17,7 @@ from ldap3.core.exceptions import LDAPException
|
||||
from redis.exceptions import ConnectionError as RedisConnectionError
|
||||
from redis.exceptions import RedisError, ResponseError
|
||||
from rest_framework.exceptions import APIException
|
||||
from sentry_sdk import HttpTransport
|
||||
from sentry_sdk import HttpTransport, get_current_scope
|
||||
from sentry_sdk import init as sentry_sdk_init
|
||||
from sentry_sdk.api import set_tag
|
||||
from sentry_sdk.integrations.argv import ArgvIntegration
|
||||
@ -27,6 +27,7 @@ from sentry_sdk.integrations.redis import RedisIntegration
|
||||
from sentry_sdk.integrations.socket import SocketIntegration
|
||||
from sentry_sdk.integrations.stdlib import StdlibIntegration
|
||||
from sentry_sdk.integrations.threading import ThreadingIntegration
|
||||
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME
|
||||
from structlog.stdlib import get_logger
|
||||
from websockets.exceptions import WebSocketException
|
||||
|
||||
@ -95,6 +96,8 @@ def traces_sampler(sampling_context: dict) -> float:
|
||||
return 0
|
||||
if _type == "websocket":
|
||||
return 0
|
||||
if CONFIG.get_bool("debug"):
|
||||
return 1
|
||||
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
||||
|
||||
|
||||
@ -167,3 +170,14 @@ def before_send(event: dict, hint: dict) -> dict | None:
|
||||
if settings.DEBUG:
|
||||
return None
|
||||
return event
|
||||
|
||||
|
||||
def get_http_meta():
|
||||
"""Get sentry-related meta key-values"""
|
||||
scope = get_current_scope()
|
||||
meta = {
|
||||
SENTRY_TRACE_HEADER_NAME: scope.get_traceparent() or "",
|
||||
}
|
||||
if bag := scope.get_baggage():
|
||||
meta[BAGGAGE_HEADER_NAME] = bag.serialize()
|
||||
return meta
|
||||
|
@ -494,86 +494,88 @@ class TestConfig(TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_db_pool(self):
|
||||
"""Test DB Config with pool"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
config.set("postgresql.use_pool", True)
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"pool": True,
|
||||
"sslcert": None,
|
||||
"sslkey": None,
|
||||
"sslmode": None,
|
||||
"sslrootcert": None,
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
"CONN_MAX_AGE": 0,
|
||||
"CONN_HEALTH_CHECKS": False,
|
||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
}
|
||||
},
|
||||
)
|
||||
# FIXME: Temporarily force pool to be deactivated.
|
||||
# See https://github.com/goauthentik/authentik/issues/14320
|
||||
# def test_db_pool(self):
|
||||
# """Test DB Config with pool"""
|
||||
# config = ConfigLoader()
|
||||
# config.set("postgresql.host", "foo")
|
||||
# config.set("postgresql.name", "foo")
|
||||
# config.set("postgresql.user", "foo")
|
||||
# config.set("postgresql.password", "foo")
|
||||
# config.set("postgresql.port", "foo")
|
||||
# config.set("postgresql.test.name", "foo")
|
||||
# config.set("postgresql.use_pool", True)
|
||||
# conf = django_db_config(config)
|
||||
# self.assertEqual(
|
||||
# conf,
|
||||
# {
|
||||
# "default": {
|
||||
# "ENGINE": "authentik.root.db",
|
||||
# "HOST": "foo",
|
||||
# "NAME": "foo",
|
||||
# "OPTIONS": {
|
||||
# "pool": True,
|
||||
# "sslcert": None,
|
||||
# "sslkey": None,
|
||||
# "sslmode": None,
|
||||
# "sslrootcert": None,
|
||||
# },
|
||||
# "PASSWORD": "foo",
|
||||
# "PORT": "foo",
|
||||
# "TEST": {"NAME": "foo"},
|
||||
# "USER": "foo",
|
||||
# "CONN_MAX_AGE": 0,
|
||||
# "CONN_HEALTH_CHECKS": False,
|
||||
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
# }
|
||||
# },
|
||||
# )
|
||||
|
||||
def test_db_pool_options(self):
|
||||
"""Test DB Config with pool"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
config.set("postgresql.use_pool", True)
|
||||
config.set(
|
||||
"postgresql.pool_options",
|
||||
base64.b64encode(
|
||||
dumps(
|
||||
{
|
||||
"max_size": 15,
|
||||
}
|
||||
).encode()
|
||||
).decode(),
|
||||
)
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"pool": {
|
||||
"max_size": 15,
|
||||
},
|
||||
"sslcert": None,
|
||||
"sslkey": None,
|
||||
"sslmode": None,
|
||||
"sslrootcert": None,
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
"CONN_MAX_AGE": 0,
|
||||
"CONN_HEALTH_CHECKS": False,
|
||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
}
|
||||
},
|
||||
)
|
||||
# def test_db_pool_options(self):
|
||||
# """Test DB Config with pool"""
|
||||
# config = ConfigLoader()
|
||||
# config.set("postgresql.host", "foo")
|
||||
# config.set("postgresql.name", "foo")
|
||||
# config.set("postgresql.user", "foo")
|
||||
# config.set("postgresql.password", "foo")
|
||||
# config.set("postgresql.port", "foo")
|
||||
# config.set("postgresql.test.name", "foo")
|
||||
# config.set("postgresql.use_pool", True)
|
||||
# config.set(
|
||||
# "postgresql.pool_options",
|
||||
# base64.b64encode(
|
||||
# dumps(
|
||||
# {
|
||||
# "max_size": 15,
|
||||
# }
|
||||
# ).encode()
|
||||
# ).decode(),
|
||||
# )
|
||||
# conf = django_db_config(config)
|
||||
# self.assertEqual(
|
||||
# conf,
|
||||
# {
|
||||
# "default": {
|
||||
# "ENGINE": "authentik.root.db",
|
||||
# "HOST": "foo",
|
||||
# "NAME": "foo",
|
||||
# "OPTIONS": {
|
||||
# "pool": {
|
||||
# "max_size": 15,
|
||||
# },
|
||||
# "sslcert": None,
|
||||
# "sslkey": None,
|
||||
# "sslmode": None,
|
||||
# "sslrootcert": None,
|
||||
# },
|
||||
# "PASSWORD": "foo",
|
||||
# "PORT": "foo",
|
||||
# "TEST": {"NAME": "foo"},
|
||||
# "USER": "foo",
|
||||
# "CONN_MAX_AGE": 0,
|
||||
# "CONN_HEALTH_CHECKS": False,
|
||||
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
# }
|
||||
# },
|
||||
# )
|
||||
|
@ -56,6 +56,7 @@ EXPOSE 3389 6636 9300
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/ldap"]
|
||||
|
@ -97,6 +97,7 @@ elif [[ "$1" == "test-all" ]]; then
|
||||
elif [[ "$1" == "healthcheck" ]]; then
|
||||
run_authentik healthcheck $(cat $MODE_FILE)
|
||||
elif [[ "$1" == "dump_config" ]]; then
|
||||
shift
|
||||
exec python -m authentik.lib.config $@
|
||||
elif [[ "$1" == "debug" ]]; then
|
||||
exec sleep infinity
|
||||
|
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.1013.0",
|
||||
"aws-cdk": "^2.1014.0",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1013.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1013.0.tgz",
|
||||
"integrity": "sha512-cbq4cOoEIZueMWenGgfI4RujS+AQ9GaMCTlW/3CnvEIhMD8j/tgZx7PTtgMuvwYrRoEeb/wTxgLPgUd5FhsoHA==",
|
||||
"version": "2.1014.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1014.0.tgz",
|
||||
"integrity": "sha512-es101rtRAClix9BncNL54iW90MiOyRv4iCC5tv/firGDnidS6pPinuK0IIFt0RO6w0+3heRxWBXg8HY+f9877w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1013.0",
|
||||
"aws-cdk": "^2.1014.0",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ EXPOSE 9000 9300 9443
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/proxy"]
|
||||
|
180
pyproject.toml
180
pyproject.toml
@ -5,100 +5,100 @@ description = ""
|
||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||
requires-python = "==3.13.*"
|
||||
dependencies = [
|
||||
"argon2-cffi",
|
||||
"celery",
|
||||
"channels",
|
||||
"channels-redis",
|
||||
"cryptography",
|
||||
"dacite",
|
||||
"deepmerge",
|
||||
"defusedxml",
|
||||
"django",
|
||||
"django-countries",
|
||||
"django-cte",
|
||||
"django-filter",
|
||||
"django-guardian",
|
||||
"django-model-utils",
|
||||
"django-pglock",
|
||||
"django-prometheus",
|
||||
"django-redis",
|
||||
"django-storages[s3]",
|
||||
"django-tenants",
|
||||
"djangorestframework",
|
||||
"djangorestframework-guardian",
|
||||
"docker",
|
||||
"drf-orjson-renderer",
|
||||
"drf-spectacular",
|
||||
"dumb-init",
|
||||
"duo-client",
|
||||
"fido2",
|
||||
"flower",
|
||||
"geoip2",
|
||||
"geopy",
|
||||
"google-api-python-client",
|
||||
"gssapi",
|
||||
"gunicorn",
|
||||
"jsonpatch",
|
||||
"jwcrypto",
|
||||
"kubernetes",
|
||||
"ldap3",
|
||||
"lxml",
|
||||
"msgraph-sdk",
|
||||
"opencontainers",
|
||||
"packaging",
|
||||
"paramiko",
|
||||
"psycopg[c, pool]",
|
||||
"pydantic",
|
||||
"pydantic-scim",
|
||||
"pyjwt",
|
||||
"pyrad",
|
||||
"python-kadmin-rs",
|
||||
"pyyaml",
|
||||
"requests-oauthlib",
|
||||
"scim2-filter-parser",
|
||||
"sentry-sdk",
|
||||
"service_identity",
|
||||
"setproctitle",
|
||||
"structlog",
|
||||
"swagger-spec-validator",
|
||||
"tenant-schemas-celery",
|
||||
"twilio",
|
||||
"ua-parser",
|
||||
"unidecode",
|
||||
"urllib3 <3",
|
||||
"uvicorn[standard]",
|
||||
"watchdog",
|
||||
"webauthn",
|
||||
"wsproto",
|
||||
"xmlsec",
|
||||
"zxcvbn",
|
||||
"argon2-cffi==23.1.0",
|
||||
"celery==5.5.2",
|
||||
"channels==4.2.2",
|
||||
"channels-redis==4.2.1",
|
||||
"cryptography==44.0.3",
|
||||
"dacite==1.9.2",
|
||||
"deepmerge==2.0",
|
||||
"defusedxml==0.7.1",
|
||||
"django==5.1.9",
|
||||
"django-countries==7.6.1",
|
||||
"django-cte==1.3.3",
|
||||
"django-filter==25.1",
|
||||
"django-guardian<3.0.0",
|
||||
"django-model-utils==5.0.0",
|
||||
"django-pglock==1.7.1",
|
||||
"django-prometheus==2.3.1",
|
||||
"django-redis==5.4.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-tenants==3.7.0",
|
||||
"djangorestframework==3.16.0",
|
||||
"djangorestframework-guardian==0.3.0",
|
||||
"docker==7.1.0",
|
||||
"drf-orjson-renderer==1.7.3",
|
||||
"drf-spectacular==0.28.0",
|
||||
"dumb-init==1.2.5.post1",
|
||||
"duo-client==5.5.0",
|
||||
"fido2==1.2.0",
|
||||
"flower==2.0.1",
|
||||
"geoip2==5.1.0",
|
||||
"geopy==2.4.1",
|
||||
"google-api-python-client==2.169.0",
|
||||
"gssapi==1.9.0",
|
||||
"gunicorn==23.0.0",
|
||||
"jsonpatch==1.33",
|
||||
"jwcrypto==1.5.6",
|
||||
"kubernetes==32.0.1",
|
||||
"ldap3==2.9.1",
|
||||
"lxml==5.4.0",
|
||||
"msgraph-sdk==1.30.0",
|
||||
"opencontainers==0.0.14",
|
||||
"packaging==25.0",
|
||||
"paramiko==3.5.1",
|
||||
"psycopg[c,pool]==3.2.9",
|
||||
"pydantic==2.11.4",
|
||||
"pydantic-scim==0.0.8",
|
||||
"pyjwt==2.10.1",
|
||||
"pyrad==2.4",
|
||||
"python-kadmin-rs==0.6.0",
|
||||
"pyyaml==6.0.2",
|
||||
"requests-oauthlib==2.0.0",
|
||||
"scim2-filter-parser==0.7.0",
|
||||
"sentry-sdk==2.28.0",
|
||||
"service-identity==24.2.0",
|
||||
"setproctitle==1.3.6",
|
||||
"structlog==25.3.0",
|
||||
"swagger-spec-validator==3.0.4",
|
||||
"tenant-schemas-celery==4.0.1",
|
||||
"twilio==9.6.1",
|
||||
"ua-parser==1.0.1",
|
||||
"unidecode==1.4.0",
|
||||
"urllib3<3",
|
||||
"uvicorn[standard]==0.34.2",
|
||||
"watchdog==6.0.0",
|
||||
"webauthn==2.5.2",
|
||||
"wsproto==1.2.0",
|
||||
"xmlsec==1.3.15",
|
||||
"zxcvbn==4.5.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"aws-cdk-lib",
|
||||
"bandit",
|
||||
"black",
|
||||
"bump2version",
|
||||
"channels[daphne]",
|
||||
"codespell",
|
||||
"colorama",
|
||||
"constructs",
|
||||
"coverage[toml]",
|
||||
"debugpy",
|
||||
"drf-jsonschema-serializer",
|
||||
"freezegun",
|
||||
"importlib-metadata",
|
||||
"k5test",
|
||||
"pdoc",
|
||||
"pytest",
|
||||
"pytest-django",
|
||||
"pytest-github-actions-annotate-failures",
|
||||
"pytest-randomly",
|
||||
"pytest-timeout",
|
||||
"requests-mock",
|
||||
"ruff",
|
||||
"selenium",
|
||||
"aws-cdk-lib==2.188.0",
|
||||
"bandit==1.8.3",
|
||||
"black==25.1.0",
|
||||
"bump2version==1.0.1",
|
||||
"channels[daphne]==4.2.2",
|
||||
"codespell==2.4.1",
|
||||
"colorama==0.4.6",
|
||||
"constructs==10.4.2",
|
||||
"coverage[toml]==7.8.0",
|
||||
"debugpy==1.8.14",
|
||||
"drf-jsonschema-serializer==3.0.0",
|
||||
"freezegun==1.5.1",
|
||||
"importlib-metadata==8.6.1",
|
||||
"k5test==0.10.4",
|
||||
"pdoc==15.0.3",
|
||||
"pytest==8.3.5",
|
||||
"pytest-django==4.11.1",
|
||||
"pytest-github-actions-annotate-failures==0.3.0",
|
||||
"pytest-randomly==3.16.0",
|
||||
"pytest-timeout==2.4.0",
|
||||
"requests-mock==1.12.1",
|
||||
"ruff==0.11.9",
|
||||
"selenium==4.32.0",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
|
@ -56,6 +56,7 @@ HEALTHCHECK --interval=5s --retries=20 --start-period=3s CMD [ "/rac", "healthch
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/rac"]
|
||||
|
@ -56,6 +56,7 @@ EXPOSE 1812/udp 9300
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/radius"]
|
||||
|
51
tests/e2e/test_flows_login_sfe.py
Normal file
51
tests/e2e/test_flows_login_sfe.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""test default login (using SFE interface) flow"""
|
||||
|
||||
from time import sleep
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
class TestFlowsLoginSFE(SeleniumTestCase):
|
||||
"""test default login flow"""
|
||||
|
||||
def login(self):
|
||||
"""Do entire login flow adjusted for SFE"""
|
||||
flow_executor = self.driver.find_element(By.ID, "flow-sfe-container")
|
||||
identification_stage = flow_executor.find_element(By.ID, "ident-form")
|
||||
|
||||
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").click()
|
||||
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
|
||||
self.user.username
|
||||
)
|
||||
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
|
||||
Keys.ENTER
|
||||
)
|
||||
|
||||
password_stage = flow_executor.find_element(By.ID, "password-form")
|
||||
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
|
||||
self.user.username
|
||||
)
|
||||
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(Keys.ENTER)
|
||||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_login(self):
|
||||
"""test default login flow"""
|
||||
self.driver.get(
|
||||
self.url(
|
||||
"authentik_core:if-flow",
|
||||
flow_slug="default-authentication-flow",
|
||||
query={"sfe": True},
|
||||
)
|
||||
)
|
||||
self.login()
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.assert_user(self.user)
|
@ -26,6 +26,7 @@ from selenium import webdriver
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.remote.command import Command
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
@ -197,7 +198,12 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
super().tearDown()
|
||||
if IS_CI:
|
||||
print("::group::Browser logs")
|
||||
for line in self.driver.get_log("browser"):
|
||||
# Very verbose way to get browser logs
|
||||
# https://github.com/SeleniumHQ/selenium/pull/15641
|
||||
# for some reason this removes the `get_log` API from Remote Webdriver
|
||||
# and only keeps it on the local Chrome web driver, even when using
|
||||
# a remote chrome driver...? (nvm the fact this was released as a minor version)
|
||||
for line in self.driver.execute(Command.GET_LOG, {"type": "browser"})["value"]:
|
||||
print(line["message"])
|
||||
if IS_CI:
|
||||
print("::endgroup::")
|
||||
@ -235,7 +241,7 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
return element
|
||||
|
||||
def login(self):
|
||||
"""Do entire login flow and check user afterwards"""
|
||||
"""Do entire login flow"""
|
||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
|
||||
|
||||
|
377
uv.lock
generated
377
uv.lock
generated
@ -265,100 +265,100 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "argon2-cffi" },
|
||||
{ name = "celery" },
|
||||
{ name = "channels" },
|
||||
{ name = "channels-redis" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "dacite" },
|
||||
{ name = "deepmerge" },
|
||||
{ name = "defusedxml" },
|
||||
{ name = "django" },
|
||||
{ name = "django-countries" },
|
||||
{ name = "django-cte" },
|
||||
{ name = "django-filter" },
|
||||
{ name = "django-guardian" },
|
||||
{ name = "django-model-utils" },
|
||||
{ name = "django-pglock" },
|
||||
{ name = "django-prometheus" },
|
||||
{ name = "django-redis" },
|
||||
{ name = "django-storages", extras = ["s3"] },
|
||||
{ name = "argon2-cffi", specifier = "==23.1.0" },
|
||||
{ name = "celery", specifier = "==5.5.2" },
|
||||
{ name = "channels", specifier = "==4.2.2" },
|
||||
{ name = "channels-redis", specifier = "==4.2.1" },
|
||||
{ name = "cryptography", specifier = "==44.0.3" },
|
||||
{ name = "dacite", specifier = "==1.9.2" },
|
||||
{ name = "deepmerge", specifier = "==2.0" },
|
||||
{ name = "defusedxml", specifier = "==0.7.1" },
|
||||
{ name = "django", specifier = "==5.1.9" },
|
||||
{ name = "django-countries", specifier = "==7.6.1" },
|
||||
{ name = "django-cte", specifier = "==1.3.3" },
|
||||
{ name = "django-filter", specifier = "==25.1" },
|
||||
{ name = "django-guardian", specifier = "<3.0.0" },
|
||||
{ name = "django-model-utils", specifier = "==5.0.0" },
|
||||
{ name = "django-pglock", specifier = "==1.7.1" },
|
||||
{ name = "django-prometheus", specifier = "==2.3.1" },
|
||||
{ name = "django-redis", specifier = "==5.4.0" },
|
||||
{ name = "django-storages", extras = ["s3"], specifier = "==1.14.6" },
|
||||
{ name = "django-tenants", git = "https://github.com/rissson/django-tenants.git?branch=authentik-fixes" },
|
||||
{ name = "djangorestframework", git = "https://github.com/authentik-community/django-rest-framework?rev=896722bab969fabc74a08b827da59409cf9f1a4e" },
|
||||
{ name = "djangorestframework-guardian" },
|
||||
{ name = "docker" },
|
||||
{ name = "drf-orjson-renderer" },
|
||||
{ name = "drf-spectacular" },
|
||||
{ name = "dumb-init" },
|
||||
{ name = "duo-client" },
|
||||
{ name = "fido2" },
|
||||
{ name = "flower" },
|
||||
{ name = "geoip2" },
|
||||
{ name = "geopy" },
|
||||
{ name = "google-api-python-client" },
|
||||
{ name = "gssapi" },
|
||||
{ name = "gunicorn" },
|
||||
{ name = "jsonpatch" },
|
||||
{ name = "jwcrypto" },
|
||||
{ name = "kubernetes" },
|
||||
{ name = "ldap3" },
|
||||
{ name = "lxml" },
|
||||
{ name = "msgraph-sdk" },
|
||||
{ name = "djangorestframework-guardian", specifier = "==0.3.0" },
|
||||
{ name = "docker", specifier = "==7.1.0" },
|
||||
{ name = "drf-orjson-renderer", specifier = "==1.7.3" },
|
||||
{ name = "drf-spectacular", specifier = "==0.28.0" },
|
||||
{ name = "dumb-init", specifier = "==1.2.5.post1" },
|
||||
{ name = "duo-client", specifier = "==5.5.0" },
|
||||
{ name = "fido2", specifier = "==1.2.0" },
|
||||
{ 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.169.0" },
|
||||
{ name = "gssapi", specifier = "==1.9.0" },
|
||||
{ name = "gunicorn", specifier = "==23.0.0" },
|
||||
{ name = "jsonpatch", specifier = "==1.33" },
|
||||
{ name = "jwcrypto", specifier = "==1.5.6" },
|
||||
{ name = "kubernetes", specifier = "==32.0.1" },
|
||||
{ name = "ldap3", specifier = "==2.9.1" },
|
||||
{ name = "lxml", specifier = "==5.4.0" },
|
||||
{ name = "msgraph-sdk", specifier = "==1.30.0" },
|
||||
{ name = "opencontainers", git = "https://github.com/BeryJu/oci-python?rev=c791b19056769cd67957322806809ab70f5bead8" },
|
||||
{ name = "packaging" },
|
||||
{ name = "paramiko" },
|
||||
{ name = "psycopg", extras = ["c", "pool"] },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-scim" },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "pyrad" },
|
||||
{ name = "python-kadmin-rs" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests-oauthlib" },
|
||||
{ name = "scim2-filter-parser" },
|
||||
{ name = "sentry-sdk" },
|
||||
{ name = "service-identity" },
|
||||
{ name = "setproctitle" },
|
||||
{ name = "structlog" },
|
||||
{ name = "swagger-spec-validator" },
|
||||
{ name = "tenant-schemas-celery" },
|
||||
{ name = "twilio" },
|
||||
{ name = "ua-parser" },
|
||||
{ name = "unidecode" },
|
||||
{ name = "packaging", specifier = "==25.0" },
|
||||
{ name = "paramiko", specifier = "==3.5.1" },
|
||||
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" },
|
||||
{ name = "pydantic", specifier = "==2.11.4" },
|
||||
{ name = "pydantic-scim", specifier = "==0.0.8" },
|
||||
{ name = "pyjwt", specifier = "==2.10.1" },
|
||||
{ name = "pyrad", specifier = "==2.4" },
|
||||
{ name = "python-kadmin-rs", specifier = "==0.6.0" },
|
||||
{ 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.28.0" },
|
||||
{ name = "service-identity", specifier = "==24.2.0" },
|
||||
{ name = "setproctitle", specifier = "==1.3.6" },
|
||||
{ name = "structlog", specifier = "==25.3.0" },
|
||||
{ name = "swagger-spec-validator", specifier = "==3.0.4" },
|
||||
{ name = "tenant-schemas-celery", specifier = "==4.0.1" },
|
||||
{ name = "twilio", specifier = "==9.6.1" },
|
||||
{ name = "ua-parser", specifier = "==1.0.1" },
|
||||
{ name = "unidecode", specifier = "==1.4.0" },
|
||||
{ name = "urllib3", specifier = "<3" },
|
||||
{ name = "uvicorn", extras = ["standard"] },
|
||||
{ name = "watchdog" },
|
||||
{ name = "webauthn" },
|
||||
{ name = "wsproto" },
|
||||
{ name = "xmlsec" },
|
||||
{ name = "zxcvbn" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = "==0.34.2" },
|
||||
{ name = "watchdog", specifier = "==6.0.0" },
|
||||
{ name = "webauthn", specifier = "==2.5.2" },
|
||||
{ name = "wsproto", specifier = "==1.2.0" },
|
||||
{ name = "xmlsec", specifier = "==1.3.15" },
|
||||
{ name = "zxcvbn", specifier = "==4.5.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "aws-cdk-lib" },
|
||||
{ name = "bandit" },
|
||||
{ name = "black" },
|
||||
{ name = "bump2version" },
|
||||
{ name = "channels", extras = ["daphne"] },
|
||||
{ name = "codespell" },
|
||||
{ name = "colorama" },
|
||||
{ name = "constructs" },
|
||||
{ name = "coverage", extras = ["toml"] },
|
||||
{ name = "debugpy" },
|
||||
{ name = "drf-jsonschema-serializer" },
|
||||
{ name = "freezegun" },
|
||||
{ name = "importlib-metadata" },
|
||||
{ name = "k5test" },
|
||||
{ name = "pdoc" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-django" },
|
||||
{ name = "pytest-github-actions-annotate-failures" },
|
||||
{ name = "pytest-randomly" },
|
||||
{ name = "pytest-timeout" },
|
||||
{ name = "requests-mock" },
|
||||
{ name = "ruff" },
|
||||
{ name = "selenium" },
|
||||
{ name = "aws-cdk-lib", specifier = "==2.188.0" },
|
||||
{ name = "bandit", specifier = "==1.8.3" },
|
||||
{ name = "black", specifier = "==25.1.0" },
|
||||
{ name = "bump2version", specifier = "==1.0.1" },
|
||||
{ name = "channels", extras = ["daphne"], specifier = "==4.2.2" },
|
||||
{ name = "codespell", specifier = "==2.4.1" },
|
||||
{ name = "colorama", specifier = "==0.4.6" },
|
||||
{ name = "constructs", specifier = "==10.4.2" },
|
||||
{ name = "coverage", extras = ["toml"], specifier = "==7.8.0" },
|
||||
{ name = "debugpy", specifier = "==1.8.14" },
|
||||
{ name = "drf-jsonschema-serializer", specifier = "==3.0.0" },
|
||||
{ name = "freezegun", specifier = "==1.5.1" },
|
||||
{ name = "importlib-metadata", specifier = "==8.6.1" },
|
||||
{ name = "k5test", specifier = "==0.10.4" },
|
||||
{ name = "pdoc", specifier = "==15.0.3" },
|
||||
{ name = "pytest", specifier = "==8.3.5" },
|
||||
{ name = "pytest-django", specifier = "==4.11.1" },
|
||||
{ name = "pytest-github-actions-annotate-failures", specifier = "==0.3.0" },
|
||||
{ name = "pytest-randomly", specifier = "==3.16.0" },
|
||||
{ name = "pytest-timeout", specifier = "==2.4.0" },
|
||||
{ name = "requests-mock", specifier = "==1.12.1" },
|
||||
{ name = "ruff", specifier = "==0.11.9" },
|
||||
{ name = "selenium", specifier = "==4.32.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -387,16 +387,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-cdk-asset-awscli-v1"
|
||||
version = "2.2.231"
|
||||
version = "2.2.235"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsii" },
|
||||
{ name = "publication" },
|
||||
{ name = "typeguard" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/b2/4a142d1d8093691c1b54b7b35f463f6defa1d0a8a08b7be2277eae73c726/aws_cdk_asset_awscli_v1-2.2.231.tar.gz", hash = "sha256:859d99e0fcdc2f6ada44090ad9f921743da3ca3a6d9f39ab06836d4c8e0fbc23", size = 17960944, upload-time = "2025-04-07T16:48:17.423Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/e8/6706ee98e9ba436aa07ca3a65d79cf40c50005f4f760f139bec0f6c3606a/aws_cdk_asset_awscli_v1-2.2.235.tar.gz", hash = "sha256:0a2023f9d32158ae86d43dfeac2ba7679e8a050cb99b7565b26192e60e57a91c", size = 19130124, upload-time = "2025-05-05T15:24:02.938Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/2d/dae06874ab3a66ad898d9c2d792c863b8b8249b203a1d8e3b36dfca44a93/aws_cdk_asset_awscli_v1-2.2.231-py3-none-any.whl", hash = "sha256:06d6b1d9e52272c315b944320f7039b47c6a6058f063fa33ab0ec06fea17bfbe", size = 17959325, upload-time = "2025-04-07T16:48:14.477Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/27/b167173d7fb784848563d596085dc8e95cabbe7b01f8a5c0ac1ed6a80c36/aws_cdk_asset_awscli_v1-2.2.235-py3-none-any.whl", hash = "sha256:701a47a97419b917ce73cf9c922a26c2895943b4b18b191e1285572b8584ae1e", size = 19128489, upload-time = "2025-05-05T15:23:59.87Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -461,7 +461,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "azure-identity"
|
||||
version = "1.21.0"
|
||||
version = "1.22.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-core" },
|
||||
@ -470,9 +470,9 @@ dependencies = [
|
||||
{ name = "msal-extensions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/a1/f1a683672e7a88ea0e3119f57b6c7843ed52650fdcac8bfa66ed84e86e40/azure_identity-1.21.0.tar.gz", hash = "sha256:ea22ce6e6b0f429bc1b8d9212d5b9f9877bd4c82f1724bfa910760612c07a9a6", size = 266445, upload-time = "2025-03-11T20:53:07.463Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/8e/1b5916f5e1696bf05b009cf7d41383cea54aa8536d4a4f6f88cca15eb6a4/azure_identity-1.22.0.tar.gz", hash = "sha256:c8f5ef23e5295c2fa300c984dd9f5e1fe43503fc25c121c37ff6a15e39b800b9", size = 263346, upload-time = "2025-05-06T20:22:24.13Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/9f/1f9f3ef4f49729ee207a712a5971a9ca747f2ca47d9cbf13cf6953e3478a/azure_identity-1.21.0-py3-none-any.whl", hash = "sha256:258ea6325537352440f71b35c3dffe9d240eae4a5126c1b7ce5efd5766bd9fd9", size = 189190, upload-time = "2025-03-11T20:53:09.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/1a/6f13d7f95f68f37303c0e00e011d498e4524e70d354b2e11ef5ae89e0ce0/azure_identity-1.22.0-py3-none-any.whl", hash = "sha256:26d6c63f2ca453c77c3e74be8613941ad074e05d0c8be135247573752c249ad8", size = 185524, upload-time = "2025-05-06T20:22:25.991Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -571,30 +571,30 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.38.8"
|
||||
version = "1.38.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
{ name = "jmespath" },
|
||||
{ name = "s3transfer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/8e/bf339382eaff15b3575d23b2b6f06769765001234c2ccaafa50a20931379/boto3-1.38.8.tar.gz", hash = "sha256:6bbc75bb51be9c5a33d07a4adf13d133c60f77b7c47bef1c46fda90b1297a867", size = 111798, upload-time = "2025-05-03T00:18:45.139Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/89/a47f62b3f81a2e3484d2a2b8dd4906c5b6e57da0af0bd59d36f99ba20baf/boto3-1.38.13.tar.gz", hash = "sha256:6633bce2b73284acce1453ca85834c7c5a59e0dbcce1170be461cc079bdcdfcf", size = 111812, upload-time = "2025-05-09T19:33:02.962Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/d9/1bd6c2a6c3d3bf1d8b0be52c39230bd1e14bb55b7ecc04f42fcb68b27343/boto3-1.38.8-py3-none-any.whl", hash = "sha256:f3a4d79f499f567d327d2d8846d02ad18244d2927f88858a42a2438f52d9a0ef", size = 139899, upload-time = "2025-05-03T00:18:42.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/25/79e219648f10d060d152542fcf3be0093120471774b99c1a7f41ceaeca9b/boto3-1.38.13-py3-none-any.whl", hash = "sha256:668400d13889d2d2fcd66ce785cc0b0fc040681f58a9c7f67daa9149a52b6c63", size = 139934, upload-time = "2025-05-09T19:33:00.855Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.38.8"
|
||||
version = "1.38.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jmespath" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/18/1ec9220e180106d8055365a9bb4926db9840211c65f5fd70a5a90b0873cf/botocore-1.38.8.tar.gz", hash = "sha256:68d739300cc94232373517b27c5570de6ae6d809a2db644f30219f5c8e0371ce", size = 13871026, upload-time = "2025-05-03T00:18:32.427Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/36/5b0faba074684744244e1e030e73fd5612bc2c38f557eec0a7f1a3d7ddd2/botocore-1.38.13.tar.gz", hash = "sha256:22feee15753cd3f9f7179d041604078a1024701497d27b22be7c6707e8d13ccb", size = 13882010, upload-time = "2025-05-09T19:32:51.172Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/66/e5a314d1e868cd35ec5c5d11360387c2a85e8d408f084616337f1a282c61/botocore-1.38.8-py3-none-any.whl", hash = "sha256:f6ae08a56fe94e18d2aa223611a3b5e94123315d0cb3cb85764b029b2326c710", size = 13531917, upload-time = "2025-05-03T00:18:26.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/df/a7a8097471d5a3bc7d408850222292d874ffc190aef7e1cacf9af770339e/botocore-1.38.13-py3-none-any.whl", hash = "sha256:de29fee43a1f02787fb5b3756ec09917d5661ed95b2b2d64797ab04196f69e14", size = 13544507, upload-time = "2025-05-09T19:32:37.727Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -750,14 +750,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
version = "8.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -979,16 +979,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.1.8"
|
||||
version = "5.1.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/40/45adc1b93435d1b418654a734b68351bb6ce0a0e5e37b2f0e9aeb1a2e233/Django-5.1.8.tar.gz", hash = "sha256:42e92a1dd2810072bcc40a39a212b693f94406d0ba0749e68eb642f31dc770b4", size = 10723602, upload-time = "2025-04-02T11:19:56.028Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/08/2e6f05494b3fc0a3c53736846034f882b82ee6351791a7815bbb45715d79/django-5.1.9.tar.gz", hash = "sha256:565881bdd0eb67da36442e9ac788bda90275386b549070d70aee86327781a4fc", size = 10710887, upload-time = "2025-05-07T14:06:45.257Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/0d/e6dd0ed898b920fec35c6eeeb9acbeb831fff19ad21c5e684744df1d4a36/Django-5.1.8-py3-none-any.whl", hash = "sha256:11b28fa4b00e59d0def004e9ee012fefbb1065a5beb39ee838983fd24493ad4f", size = 8277130, upload-time = "2025-04-02T11:19:51.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/d1/d8b6b8250b84380d5a123e099ad3298a49407d81598faa13b43a2c6d96d7/django-5.1.9-py3-none-any.whl", hash = "sha256:2fd1d4a0a66a5ba702699eb692e75b0d828b73cc2f4e1fc4b6a854a918967411", size = 8277363, upload-time = "2025-05-07T14:06:37.426Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1354,16 +1354,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "geoip2"
|
||||
version = "5.0.1"
|
||||
version = "5.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "maxminddb" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/d7/21cfa1072b8ec5937c6af0cf8b624b4be9b44a7ca82f4335900df5482076/geoip2-5.0.1.tar.gz", hash = "sha256:90af8b6d3687f3bef251f2708ad017b30d627d1144c0040eabc4c9017a807d86", size = 175854, upload-time = "2025-01-28T23:22:04.689Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/5f/902835f485d1c423aca9097a0e91925d6a706049f64e678ec781b168734d/geoip2-5.1.0.tar.gz", hash = "sha256:ee3f87f0ce9325eb6484fe18cbd9771a03d0a2bad1dd156fa3584fafa562d39a", size = 268166, upload-time = "2025-05-05T19:40:29.202Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/6c/4f17beb65444cd0b8e602c2ea64ef1ec8ff32e1aa8c6a3be3fad7113b947/geoip2-5.0.1-py3-none-any.whl", hash = "sha256:128b7c9e6b55fb66178428a9400bd4f8b011cf64f147b1ed9e3a4766e61a9b78", size = 28064, upload-time = "2025-01-28T23:22:01.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/43/aa9a10d0c971d0a0e353111a97913357f9271fb9a9867ec1053f79ca61be/geoip2-5.1.0-py3-none-any.whl", hash = "sha256:445a058995ad5bb3e665ae716413298d4383b1fb38d372ad59b9b405f6b0ca19", size = 27691, upload-time = "2025-05-05T19:40:26.082Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1412,16 +1412,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "google-auth"
|
||||
version = "2.39.0"
|
||||
version = "2.40.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cachetools" },
|
||||
{ name = "pyasn1-modules" },
|
||||
{ name = "rsa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/8e/8f45c9a32f73e786e954b8f9761c61422955d23c45d1e8c347f9b4b59e8e/google_auth-2.39.0.tar.gz", hash = "sha256:73222d43cdc35a3aeacbfdcaf73142a97839f10de930550d89ebfe1d0a00cde7", size = 274834, upload-time = "2025-04-14T17:44:49.402Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/a5/38c21d0e731bb716cffcf987bd9a3555cb95877ab4b616cfb96939933f20/google_auth-2.40.1.tar.gz", hash = "sha256:58f0e8416a9814c1d86c9b7f6acf6816b51aba167b2c76821965271bac275540", size = 280975, upload-time = "2025-05-07T01:04:55.3Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/12/ad37a1ef86006d0a0117fc06a4a00bd461c775356b534b425f00dde208ea/google_auth-2.39.0-py2.py3-none-any.whl", hash = "sha256:0150b6711e97fb9f52fe599f55648950cc4540015565d8fbb31be2ad6e1548a2", size = 212319, upload-time = "2025-04-14T17:44:47.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/b1/1272c6e80847ba5349f5ccb7574596393d1e222543f5003cb810865c3575/google_auth-2.40.1-py2.py3-none-any.whl", hash = "sha256:ed4cae4f5c46b41bae1d19c036e06f6c371926e97b19e816fc854eff811974ee", size = 216101, upload-time = "2025-05-07T01:04:53.612Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1680,7 +1680,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "jsii"
|
||||
version = "1.111.0"
|
||||
version = "1.112.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
@ -1691,9 +1691,9 @@ dependencies = [
|
||||
{ name = "typeguard" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/29/e0/f1edbe7adb75c58ef13f4b24a8f2521cc3f7f5bd79751d071900065cf0a7/jsii-1.111.0.tar.gz", hash = "sha256:db523ab9b6575c84d6ed8779cdbdc739abd48a7cb0723b66beb84c1a9dc31c7c", size = 624365, upload-time = "2025-04-02T16:35:49.643Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/3e/270b5236035fc7bb2cdd7f55ea25f85489d35d971870cbec32c3d9e99d7f/jsii-1.112.0.tar.gz", hash = "sha256:6b7d19f361c2565b76828ecbe8cbed8b8d6028a82aa98a46b206a4ee5083157e", size = 624533, upload-time = "2025-05-07T14:45:52.574Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/8a/d3a80a0b0ecb2c175eacbe48542695213ef4315b2e6bd62bafd244c06ae0/jsii-1.111.0-py3-none-any.whl", hash = "sha256:3084e31173e73d2eefee678c8ee31aa49428830509043057a421a4c0dde94434", size = 600503, upload-time = "2025-04-02T16:35:48.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/af/8554b632e2b82f37a7422782aba5db2a1fbff4887faa7ec850818def8407/jsii-1.112.0-py3-none-any.whl", hash = "sha256:6510c223074d9b206fd0570849a791e4d9ecfff7ffe68428de73870cea9f55a1", size = 600681, upload-time = "2025-05-07T14:45:51.136Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1881,20 +1881,24 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "maxminddb"
|
||||
version = "2.6.3"
|
||||
version = "2.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/ae/422ec0f3b6a40f23de9477c42fce90126a3994dd51d06b50582973c0088e/maxminddb-2.6.3.tar.gz", hash = "sha256:d2c3806baa7aa047aa1bac7419e7e353db435f88f09d51106a84dbacf645d254", size = 181376, upload-time = "2025-01-09T16:12:13.7Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/10/7a7cf5219b74b19ea1834b43256e114564e8a845f447446ac821e1b9951e/maxminddb-2.7.0.tar.gz", hash = "sha256:23a715ed3b3aed07adae4beeed06c51fd582137b5ae13d3c6e5ca4890f70ebbf", size = 196583, upload-time = "2025-05-05T19:31:43.957Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/e3/a3218d7cd35c930a08f7d7301334f9c85aa0a28dbac3f50e3d43f3d70734/maxminddb-2.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9580b2cd017185db07baacd9d629ca01f3fe6f236528681c88a0209725376e9c", size = 35235, upload-time = "2025-01-09T16:10:57.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/40/11f23d1c1f6654618d87e995f56a789f00c1c07d5c986f9b14d81f04f90c/maxminddb-2.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47828bed767b82c219ba7aa65f0cb03d7f7443d7270259ce931e133a40691d34", size = 35021, upload-time = "2025-01-09T16:10:58.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/7e/883adcb107fb45916328ecb40f980cc598dbcc7dfd2ccc871851c40836d6/maxminddb-2.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77112cb1a2e381de42c443d1bf222c58b9da203183bb2008dd370c3d2a587a4e", size = 90068, upload-time = "2025-01-09T16:10:59.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/87/b57cf9ef4cf8b076f3b25df949b57c7b3ee0f4543f1f76f445afd313b96b/maxminddb-2.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:448d062e95242e3088df85fe7ed3f2890a9f4aea924bde336e9ff5d2337ca5fd", size = 89506, upload-time = "2025-01-09T16:11:01.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/f8/cf746032f267ee25bd32f70d71a63e857fec91e19a0907db885bdbb7b0c1/maxminddb-2.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a59d72bf373c61da156fd43e2be6da802f68370a50a2205de84ee76916e05f9f", size = 87612, upload-time = "2025-01-09T16:11:02.362Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/9e/ff5c93e8e589c1544cad2a457c1b7e4169a256c8655928266a9de6f21cac/maxminddb-2.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e867852037a8a26a24cfcf31b697dce63d488e1617af244c2895568d8f6c7a31", size = 92310, upload-time = "2025-01-09T16:11:03.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/44/56ed56377ba8c99f7eb3101479c063d46f18e5f0a9070432d74a2ed15f82/maxminddb-2.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a1586260eac831d61c2665b26ca1ae3ad00caca57c8031346767f4527025311", size = 91227, upload-time = "2025-01-09T16:11:04.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/58/cdb1a7c18a1946ad006657b52cb499e489d2b28a62490fd5aee14b356a55/maxminddb-2.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6eb23f842a72ab3096f9f9b1c292f4feb55a8d758567cb6d77637c2257a3187c", size = 92126, upload-time = "2025-01-09T16:11:06.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/94/4b37ffa77f8921a549805a62ce62f6fa453ea3c59c0dfcd584770fc59a8c/maxminddb-2.6.3-cp313-cp313-win32.whl", hash = "sha256:acf46e20709a27d2b519669888e3f53a37bc4204b98a0c690664c48ff8cb1364", size = 34751, upload-time = "2025-01-09T16:11:09.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/af/638811134e1a33cf75c2d2be1b0b9b90dd1f43216a4ef1f24e223f646b46/maxminddb-2.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:3015afb00e6168837938dbe5fda40ace37442c22b292ccee27c1690fbf6078ed", size = 36790, upload-time = "2025-01-09T16:11:10.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/0e/68a558e11e8a2aaeb1b28be27c784052dcccd780175fa9e3d2693274e8d6/maxminddb-2.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2328575e2d2ab6179acf93c09745e9af10eb92aaa305cb5bd0f7c307d0dd398e", size = 35328, upload-time = "2025-05-05T19:30:35.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/ff/2c98dda0d0aaa09dbe9a4030bd9ab056e0bf6c6559215e34185e2fd62d50/maxminddb-2.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c4f71a72f3dbdc2abd58c36ad0ad4bd936781354feee8538614d2170223675f0", size = 35098, upload-time = "2025-05-05T19:30:36.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/cb/99d1650daa24a9acb55c81412fcefa5d95b7e80a872876a902e14f33ec4d/maxminddb-2.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566e7ea8296ad126a24df52e6c37382dc9660c414ceea4c4c687bbca2d522c28", size = 90193, upload-time = "2025-05-05T19:30:37.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/15/53ceb43e1e1e7493a66fb9a3b2d3248198316d2dbe746c585591276f1aad/maxminddb-2.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c10d94df0d5ea22873a5dc1af24d8972de0a22841dbd90a7e450f66a6f11ed21", size = 94695, upload-time = "2025-05-05T19:30:38.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/d7/e26d168d85e2503232d5df2a847641024afd11405fd5132816728cc9e399/maxminddb-2.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7f0e9a4db3c986f208dd4359e9d9e776e28ce8aae540da6f1a733fae3bb67ac", size = 91306, upload-time = "2025-05-05T19:30:40.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/b5/4bb9330ee29efb0c515cb8c6c500f367021c163ebb81380789e6ac846f8b/maxminddb-2.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef41949246035af8cb5970bee2e94bbc894203312fd6fb55cbd4fe30c6e44374", size = 89620, upload-time = "2025-05-05T19:30:41.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/e5/1335e42615b57fd821a6c606119cb4babd85bc88839f8dbae0b5bb082d04/maxminddb-2.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:531be1066697b57928bce2ac9cb7e705b8cebdfa2e42dfbebc92b75fc53ad22f", size = 87751, upload-time = "2025-05-05T19:30:42.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/9a/af47d3f7a15a49be61315f29bb0e232c1f5040f3afc685509cee1ebdaef7/maxminddb-2.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:265e938c12628fceb71665e28bfca206ee9d8ae6ac18282cbfc544753ccc8b9b", size = 93328, upload-time = "2025-05-05T19:30:43.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/43/ecda2cc5a7ffae034692374401f97c3ef8fe15f22826ae2784b38ecf0cfd/maxminddb-2.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7b101cf6b79db4c046c9c9b157bb9730308074749c442f50d52a7a0e5d765357", size = 91701, upload-time = "2025-05-05T19:30:44.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/14/8edd17bdeaddd9d7d138008d6fc14baacdda418a5346d09215d14870dfd2/maxminddb-2.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:faecf825f812d54e1cb053e75358656b280af1ea4b6f53b3f1a98c3f9fa41a46", size = 98466, upload-time = "2025-05-05T19:30:46.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/6f/cefabf7868b5406f4df04c3f4cb95dc802c1ad1b05f26046a4584a235268/maxminddb-2.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:dd266b3060bb6b6b05009b04ca93787fab0a00f16638827d34bab50cfdf68dd4", size = 96255, upload-time = "2025-05-05T19:30:47.785Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/4a/f604c942dc9d3e755601831ee101198d3090c0dfa5485e2aaf3245075bf3/maxminddb-2.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f30bdd4c618c372c0f4f981f2241aad8e3ab9c361bb1d299f213e9a4c2a3fd8", size = 92190, upload-time = "2025-05-05T19:30:49.052Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/6f/563058bc28704b24fa04830acc40abca03debcaa2a12195d8269b490475b/maxminddb-2.7.0-cp313-cp313-win32.whl", hash = "sha256:023f23654b38345965cab3e33465a4b82edb2250ba7c6db5c175a872645c35c5", size = 34699, upload-time = "2025-05-05T19:30:50.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/84/33e0389d97ca9bc7e902c1f4a74e626349043c942ba0b6458fa96cbea0a8/maxminddb-2.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:f81d678ab25d4867f95fb44cce3c67f6157d25dc8846191fd4eb0e38f49a263f", size = 36723, upload-time = "2025-05-05T19:30:51.799Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2061,7 +2065,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.28.0"
|
||||
version = "1.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-identity" },
|
||||
@ -2071,9 +2075,9 @@ dependencies = [
|
||||
{ name = "microsoft-kiota-serialization-text" },
|
||||
{ name = "msgraph-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/41/40bb3c630ca026182aefd79a9862ef4a1917b1161c83690c858d714788f5/msgraph_sdk-1.28.0.tar.gz", hash = "sha256:b2d64b7bd711ad285fc2c090dd524853a026848732e1c83874fe34561805350d", size = 6121069, upload-time = "2025-04-15T11:39:08.184Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/4a/4ff19671f6ea06f98fb2405f73a90350e4719ccc692e85e9e0c2fa066826/msgraph_sdk-1.30.0.tar.gz", hash = "sha256:59e30af6d7244c9009146d620c331e169701b651317746b16f561e2e2452e73f", size = 6608744, upload-time = "2025-05-13T13:09:12.594Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/58/d8e9593ea81779d503831b5b06c8d9881d5affefe3df99ca20112c969e6f/msgraph_sdk-1.28.0-py3-none-any.whl", hash = "sha256:bd33b186371dfa8ed6375dfda92eef0931485633e69b06c001ce3c2fd3658f18", size = 25091309, upload-time = "2025-04-15T11:39:04.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/95/451ec4db8a924274a1f7260809ea03fe9c2b446d84dc5238e92e49a1b522/msgraph_sdk-1.30.0-py3-none-any.whl", hash = "sha256:6748f5cdb5ddbcff9e4f3fb073dd0a604cb00e1cf285dd0fea6969c93ba8282f", size = 27140767, upload-time = "2025-05-13T13:09:07.718Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2153,42 +2157,42 @@ source = { git = "https://github.com/BeryJu/oci-python?rev=c791b19056769cd679573
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-api"
|
||||
version = "1.32.1"
|
||||
version = "1.33.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "deprecated" },
|
||||
{ name = "importlib-metadata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/40/2359245cd33641c2736a0136a50813352d72f3fc209de28fb226950db4a1/opentelemetry_api-1.32.1.tar.gz", hash = "sha256:a5be71591694a4d9195caf6776b055aa702e964d961051a0715d05f8632c32fb", size = 64138, upload-time = "2025-04-15T16:02:13.97Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/ca/920a73b4a11cd271ba1c62f34dba27d7783996a6a7ac0bac7c83b230736d/opentelemetry_api-1.33.0.tar.gz", hash = "sha256:cc4380fd2e6da7dcb52a828ea81844ed1f4f2eb638ca3c816775109d93d58ced", size = 65000, upload-time = "2025-05-09T14:56:00.967Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/f2/89ea3361a305466bc6460a532188830351220b5f0851a5fa133155c16eca/opentelemetry_api-1.32.1-py3-none-any.whl", hash = "sha256:bbd19f14ab9f15f0e85e43e6a958aa4cb1f36870ee62b7fd205783a112012724", size = 65287, upload-time = "2025-04-15T16:01:49.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/c4/26c7ec8e51c19632f42503dbabed286c261fb06f8f61ffd348690e36958a/opentelemetry_api-1.33.0-py3-none-any.whl", hash = "sha256:158df154f628e6615b65fdf6e59f99afabea7213e72c5809dd4adf06c0d997cd", size = 65772, upload-time = "2025-05-09T14:55:38.395Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-sdk"
|
||||
version = "1.32.1"
|
||||
version = "1.33.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/65/2069caef9257fae234ca0040d945c741aa7afbd83a7298ee70fc0bc6b6f4/opentelemetry_sdk-1.32.1.tar.gz", hash = "sha256:8ef373d490961848f525255a42b193430a0637e064dd132fd2a014d94792a092", size = 161044, upload-time = "2025-04-15T16:02:28.905Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/0a/b7ae406175a2798a767e12db223e842911d9c398eea100c41c989afd2aa8/opentelemetry_sdk-1.33.0.tar.gz", hash = "sha256:a7fc56d1e07b218fcc316b24d21b59d3f1967b2ca22c217b05da3a26b797cc68", size = 161381, upload-time = "2025-05-09T14:56:12.347Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/00/d3976cdcb98027aaf16f1e980e54935eb820872792f0eaedd4fd7abb5964/opentelemetry_sdk-1.32.1-py3-none-any.whl", hash = "sha256:bba37b70a08038613247bc42beee5a81b0ddca422c7d7f1b097b32bf1c7e2f17", size = 118989, upload-time = "2025-04-15T16:02:08.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/34/831f5d9ae9375c9ba2446cb3cc0be79d8d73b78f813c9567e1615c2624f6/opentelemetry_sdk-1.33.0-py3-none-any.whl", hash = "sha256:bed376b6d37fbf00688bb65edfee817dd01d48b8559212831437529a6066049a", size = 118861, upload-time = "2025-05-09T14:55:56.956Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-semantic-conventions"
|
||||
version = "0.53b1"
|
||||
version = "0.54b0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "deprecated" },
|
||||
{ name = "opentelemetry-api" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/b6/3c56e22e9b51bcb89edab30d54830958f049760bbd9ab0a759cece7bca88/opentelemetry_semantic_conventions-0.53b1.tar.gz", hash = "sha256:4c5a6fede9de61211b2e9fc1e02e8acacce882204cd770177342b6a3be682992", size = 114350, upload-time = "2025-04-15T16:02:29.793Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/8c/bc970d1599ff40b7913c953a95195addf11c81a27cc85d5ed568e9f8c57f/opentelemetry_semantic_conventions-0.54b0.tar.gz", hash = "sha256:467b739977bdcb079af1af69f73632535cdb51099d5e3c5709a35d10fe02a9c9", size = 118646, upload-time = "2025-05-09T14:56:13.596Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/27/6b/a8fb94760ef8da5ec283e488eb43235eac3ae7514385a51b6accf881e671/opentelemetry_semantic_conventions-0.53b1-py3-none-any.whl", hash = "sha256:21df3ed13f035f8f3ea42d07cbebae37020367a53b47f1ebee3b10a381a00208", size = 188443, upload-time = "2025-04-15T16:02:10.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/aa/f7c46c19aee189e0123ef7209eaafc417e242b2073485dfb40523d6d8612/opentelemetry_semantic_conventions-0.54b0-py3-none-any.whl", hash = "sha256:fad7c1cf8908fd449eb5cf9fbbeefb301acf4bc995101f85277899cec125d823", size = 194937, upload-time = "2025-05-09T14:55:58.562Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2286,11 +2290,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.7"
|
||||
version = "4.3.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2392,14 +2396,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.7"
|
||||
version = "3.2.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/16/ca27b38762a630b70243f51eb6a728f903a17cddc4961626fa540577aba6/psycopg-3.2.7.tar.gz", hash = "sha256:9afa609c7ebf139827a38c0bf61be9c024a3ed743f56443de9d38e1efc260bf3", size = 157238, upload-time = "2025-04-30T13:05:22.867Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/eb/6e32d259437125a17b0bc2624e06c86149c618501da1dcbc8539b2684f6f/psycopg-3.2.7-py3-none-any.whl", hash = "sha256:d39747d2d5b9658b69fa462ad21d31f1ba4a5722ad1d0cb952552bc0b4125451", size = 200028, upload-time = "2025-04-30T12:59:32.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@ -2412,9 +2416,9 @@ pool = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.2.7"
|
||||
version = "3.2.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/13/74e41e5195e6a0a02b9f1e3560bc714021b725e89a40f5879df58d4189c6/psycopg_c-3.2.7.tar.gz", hash = "sha256:14455cf71ed29fdfa725c550f8c58056a852bb27b55eb59e3a0f127ca92751a3", size = 609707, upload-time = "2025-04-30T13:05:24.834Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/7f/6147cb842081b0b32692bf5a0fdf58e9ac95418ebac1184d4431ec44b85f/psycopg_c-3.2.9.tar.gz", hash = "sha256:8c9f654f20c6c56bddc4543a3caab236741ee94b6732ab7090b95605502210e2", size = 609538, upload-time = "2025-05-13T16:11:19.856Z" }
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-pool"
|
||||
@ -2666,14 +2670,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest-timeout"
|
||||
version = "2.3.1"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/93/0d/04719abc7a4bdb3a7a1f968f24b0f5253d698c9cc94975330e9d3145befb/pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9", size = 17697, upload-time = "2024-03-07T21:04:01.069Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/03/27/14af9ef8321f5edc7527e47def2a21d8118c6f329a9342cc61387a0c0599/pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e", size = 14148, upload-time = "2024-03-07T21:03:58.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2870,27 +2874,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.8"
|
||||
version = "0.11.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/52/f6/adcf73711f31c9f5393862b4281c875a462d9f639f4ccdf69dc368311c20/ruff-0.11.8.tar.gz", hash = "sha256:6d742d10626f9004b781f4558154bb226620a7242080e11caeffab1a40e99df8", size = 4086399, upload-time = "2025-05-01T14:53:24.459Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/60/c6aa9062fa518a9f86cb0b85248245cddcd892a125ca00441df77d79ef88/ruff-0.11.8-py3-none-linux_armv6l.whl", hash = "sha256:896a37516c594805e34020c4a7546c8f8a234b679a7716a3f08197f38913e1a3", size = 10272473, upload-time = "2025-05-01T14:52:37.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e4/0325e50d106dc87c00695f7bcd5044c6d252ed5120ebf423773e00270f50/ruff-0.11.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab86d22d3d721a40dd3ecbb5e86ab03b2e053bc93c700dc68d1c3346b36ce835", size = 11040862, upload-time = "2025-05-01T14:52:41.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/27/b87ea1a7be37fef0adbc7fd987abbf90b6607d96aa3fc67e2c5b858e1e53/ruff-0.11.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:258f3585057508d317610e8a412788cf726efeefa2fec4dba4001d9e6f90d46c", size = 10385273, upload-time = "2025-05-01T14:52:43.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f7/3346161570d789045ed47a86110183f6ac3af0e94e7fd682772d89f7f1a1/ruff-0.11.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727d01702f7c30baed3fc3a34901a640001a2828c793525043c29f7614994a8c", size = 10578330, upload-time = "2025-05-01T14:52:45.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/c3/327fb950b4763c7b3784f91d3038ef10c13b2d42322d4ade5ce13a2f9edb/ruff-0.11.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3dca977cc4fc8f66e89900fa415ffe4dbc2e969da9d7a54bfca81a128c5ac219", size = 10122223, upload-time = "2025-05-01T14:52:47.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/c7/ba686bce9adfeb6c61cb1bbadc17d58110fe1d602f199d79d4c880170f19/ruff-0.11.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c657fa987d60b104d2be8b052d66da0a2a88f9bd1d66b2254333e84ea2720c7f", size = 11697353, upload-time = "2025-05-01T14:52:50.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/8e/a4fb4a1ddde3c59e73996bb3ac51844ff93384d533629434b1def7a336b0/ruff-0.11.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f2e74b021d0de5eceb8bd32919f6ff8a9b40ee62ed97becd44993ae5b9949474", size = 12375936, upload-time = "2025-05-01T14:52:52.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/a1/9529cb1e2936e2479a51aeb011307e7229225df9ac64ae064d91ead54571/ruff-0.11.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b5ef39820abc0f2c62111f7045009e46b275f5b99d5e59dda113c39b7f4f38", size = 11850083, upload-time = "2025-05-01T14:52:55.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/94/8f7eac4c612673ae15a4ad2bc0ee62e03c68a2d4f458daae3de0e47c67ba/ruff-0.11.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1dba3135ca503727aa4648152c0fa67c3b1385d3dc81c75cd8a229c4b2a1458", size = 14005834, upload-time = "2025-05-01T14:52:58.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/7c/6f63b46b2be870cbf3f54c9c4154d13fac4b8827f22fa05ac835c10835b2/ruff-0.11.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f024d32e62faad0f76b2d6afd141b8c171515e4fb91ce9fd6464335c81244e5", size = 11503713, upload-time = "2025-05-01T14:53:01.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/91/57de411b544b5fe072779678986a021d87c3ee5b89551f2ca41200c5d643/ruff-0.11.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d365618d3ad747432e1ae50d61775b78c055fee5936d77fb4d92c6f559741948", size = 10457182, upload-time = "2025-05-01T14:53:03.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/49/cfe73e0ce5ecdd3e6f1137bf1f1be03dcc819d1bfe5cff33deb40c5926db/ruff-0.11.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4d9aaa91035bdf612c8ee7266153bcf16005c7c7e2f5878406911c92a31633cb", size = 10101027, upload-time = "2025-05-01T14:53:06.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/21/a5cfe47c62b3531675795f38a0ef1c52ff8de62eaddf370d46634391a3fb/ruff-0.11.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0eba551324733efc76116d9f3a0d52946bc2751f0cd30661564117d6fd60897c", size = 11111298, upload-time = "2025-05-01T14:53:08.825Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/98/f76225f87e88f7cb669ae92c062b11c0a1e91f32705f829bd426f8e48b7b/ruff-0.11.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:161eb4cff5cfefdb6c9b8b3671d09f7def2f960cee33481dd898caf2bcd02304", size = 11566884, upload-time = "2025-05-01T14:53:11.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/7e/fff70b02e57852fda17bd43f99dda37b9bcf3e1af3d97c5834ff48d04715/ruff-0.11.8-py3-none-win32.whl", hash = "sha256:5b18caa297a786465cc511d7f8be19226acf9c0a1127e06e736cd4e1878c3ea2", size = 10451102, upload-time = "2025-05-01T14:53:14.303Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/a9/eaa571eb70648c9bde3120a1d5892597de57766e376b831b06e7c1e43945/ruff-0.11.8-py3-none-win_amd64.whl", hash = "sha256:6e70d11043bef637c5617297bdedec9632af15d53ac1e1ba29c448da9341b0c4", size = 11597410, upload-time = "2025-05-01T14:53:16.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/be/f6b790d6ae98f1f32c645f8540d5c96248b72343b0a56fab3a07f2941897/ruff-0.11.8-py3-none-win_arm64.whl", hash = "sha256:304432e4c4a792e3da85b7699feb3426a0908ab98bf29df22a31b0cdd098fac2", size = 10713129, upload-time = "2025-05-01T14:53:22.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2919,7 +2923,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "selenium"
|
||||
version = "4.31.0"
|
||||
version = "4.32.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
@ -2929,22 +2933,22 @@ dependencies = [
|
||||
{ name = "urllib3", extra = ["socks"] },
|
||||
{ name = "websocket-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/bf/642cce8b5a9edad8e4880fdefbeb24f69bec2086b1121c63f883c412b797/selenium-4.31.0.tar.gz", hash = "sha256:441cffc436a2e6659fe3cfb012692435652efd38b0d368d16f661a5db47825f5", size = 855418, upload-time = "2025-04-05T00:43:06.447Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/2d/fafffe946099033ccf22bf89e12eede14c1d3c5936110c5f6f2b9830722c/selenium-4.32.0.tar.gz", hash = "sha256:b9509bef4056f4083772abb1ae19ff57247d617a29255384b26be6956615b206", size = 870997, upload-time = "2025-05-02T20:35:27.325Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/53/212db779d2481b0a8428365960596f8d5a4d482ae12c441d0507fd54aaf2/selenium-4.31.0-py3-none-any.whl", hash = "sha256:7b8b8d5e424d7133cb7aa656263b19ac505ec26d65c0f921a696e7e2c5ccd95b", size = 9350584, upload-time = "2025-04-05T00:43:04.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/37/d07ed9d13e571b2115d4ed6956d156c66816ceec0b03b2e463e80d09f572/selenium-4.32.0-py3-none-any.whl", hash = "sha256:c4d9613f8a45693d61530c9660560fadb52db7d730237bc788ddedf442391f97", size = 9369668, upload-time = "2025-05-02T20:35:24.726Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.27.0"
|
||||
version = "2.28.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/b6/a92ae6fa6d7e6e536bc586776b1669b84fb724dfe21b8ff08297f2d7c969/sentry_sdk-2.27.0.tar.gz", hash = "sha256:90f4f883f9eff294aff59af3d58c2d1b64e3927b28d5ada2b9b41f5aeda47daf", size = 323556, upload-time = "2025-04-24T10:09:37.927Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/bb/6a41b2e0e9121bed4d2ec68d50568ab95c49f4744156a9bbb789c866c66d/sentry_sdk-2.28.0.tar.gz", hash = "sha256:14d2b73bc93afaf2a9412490329099e6217761cbab13b6ee8bc0e82927e1504e", size = 325052, upload-time = "2025-05-12T07:53:12.785Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/8b/fb496a45854e37930b57564a20fb8e90dd0f8b6add0491527c00f2163b00/sentry_sdk-2.27.0-py2.py3-none-any.whl", hash = "sha256:c58935bfff8af6a0856d37e8adebdbc7b3281c2b632ec823ef03cd108d216ff0", size = 340786, upload-time = "2025-04-24T10:09:35.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/4e/b1575833094c088dfdef63fbca794518860fcbc8002aadf51ebe8b6a387f/sentry_sdk-2.28.0-py2.py3-none-any.whl", hash = "sha256:51496e6cb3cb625b99c8e08907c67a9112360259b0ef08470e532c3ab184a232", size = 341693, upload-time = "2025-05-12T07:53:10.882Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2996,11 +3000,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "80.3.1"
|
||||
version = "80.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/dc/3976b322de9d2e87ed0007cf04cc7553969b6c7b3f48a565d0333748fbcd/setuptools-80.3.1.tar.gz", hash = "sha256:31e2c58dbb67c99c289f51c16d899afedae292b978f8051efaf6262d8212f927", size = 1315082, upload-time = "2025-05-04T18:47:04.397Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/32/0cc40fe41fd2adb80a2f388987f4f8db3c866c69e33e0b4c8b093fdf700e/setuptools-80.4.0.tar.gz", hash = "sha256:5a78f61820bc088c8e4add52932ae6b8cf423da2aff268c23f813cfbb13b4006", size = 1315008, upload-time = "2025-05-09T20:42:27.972Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/53/7e/5d8af3317ddbf9519b687bd1c39d8737fde07d97f54df65553faca5cffb1/setuptools-80.3.1-py3-none-any.whl", hash = "sha256:ea8e00d7992054c4c592aeb892f6ad51fe1b4d90cc6947cc45c45717c40ec537", size = 1201172, upload-time = "2025-05-04T18:47:02.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812, upload-time = "2025-05-09T20:42:25.325Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3095,14 +3099,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tenant-schemas-celery"
|
||||
version = "3.0.0"
|
||||
version = "4.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "celery" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/fe/cfe19eb7cc3ad8e39d7df7b7c44414bf665b6ac6660c998eb498f89d16c6/tenant_schemas_celery-3.0.0.tar.gz", hash = "sha256:6be3ae1a5826f262f0f3dd343c6a85a34a1c59b89e04ae37de018f36562fed55", size = 15954, upload-time = "2024-05-19T11:16:41.837Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/f8/cf055bf171b5d83d6fe96f1840fba90d3d274be2b5c35cd21b873302b128/tenant_schemas_celery-4.0.1.tar.gz", hash = "sha256:8b8f055fcd82aa53274c09faf88653a935241518d93b86ab2d43a3df3b70c7f8", size = 18870, upload-time = "2025-04-22T18:23:51.061Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/2c/376e1e641ad08b374c75d896468a7be2e6906ce3621fd0c9f9dc09ff1963/tenant_schemas_celery-3.0.0-py3-none-any.whl", hash = "sha256:ca0f69e78ef698eb4813468231df5a0ab6a660c08e657b65f5ac92e16887eec8", size = 18108, upload-time = "2024-05-19T11:16:39.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/a8/fd663c461550d6fedfb24e987acc1557ae5b6615ca08fc6c70dbaaa88aa5/tenant_schemas_celery-4.0.1-py3-none-any.whl", hash = "sha256:d06a3ff6956db3a95168ce2051b7bff2765f9ce0d070e14df92f07a2b60ae0a0", size = 21364, upload-time = "2025-04-22T18:23:49.899Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3156,7 +3160,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "twilio"
|
||||
version = "9.6.0"
|
||||
version = "9.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@ -3164,9 +3168,9 @@ dependencies = [
|
||||
{ name = "pyjwt" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/91/e9/ffc6e52465ffc16fad31fa64aea4e10e06cb4803447310c539c6fd66e859/twilio-9.6.0.tar.gz", hash = "sha256:bcb6cbc7f1dad09717d48d3e610573b6a55fa4a1f6fd1006f5b59cf6878b5562", size = 986499, upload-time = "2025-05-05T10:48:17.921Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/78/453ff0d35442c53490c22d077f580684a2352846c721d3e01f4c6dfa85bd/twilio-9.6.1.tar.gz", hash = "sha256:bb80b31d4d9e55c33872efef7fb99373149ed4093f21c56cf582797da45862f5", size = 987002, upload-time = "2025-05-13T09:56:55.183Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/04/1d9f452b1089c634bd6d64b40b9002c935b8214e9b08a7cbbfef204c8186/twilio-9.6.0-py2.py3-none-any.whl", hash = "sha256:19e8554c56324186973dcb3121de34626755db15331767e3021a2e23f80c6a3b", size = 1859151, upload-time = "2025-05-05T10:48:15.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/f4/36fe2566a3ad7f71a89fd28ea2ebb6b2aa05c3a4d5a55b3ca6c358768c6b/twilio-9.6.1-py2.py3-none-any.whl", hash = "sha256:441fdab61b9a204eef770368380b962cbf08dc0fe9f757fe4b1d63ced37ddeed", size = 1859407, upload-time = "2025-05-13T09:56:53.094Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3483,12 +3487,17 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "xmlsec"
|
||||
version = "1.3.14"
|
||||
version = "1.3.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "lxml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/5b/244459b51dfe91211c1d9ec68fb5307dfc51e014698f52de575d25f753e0/xmlsec-1.3.14.tar.gz", hash = "sha256:934f804f2f895bcdb86f1eaee236b661013560ee69ec108d29cdd6e5f292a2d9", size = 68854, upload-time = "2024-04-17T19:34:29.388Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6b/0b/d851367799b865500efd0b255c39fc5d30892ea28c1569ca185a76d19576/xmlsec-1.3.15.tar.gz", hash = "sha256:baa856b83d0012e278e6f6cbec96ac8128de667ca9fa9a2eeb02c752e816f6d8", size = 114117, upload-time = "2025-03-11T22:37:00.567Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/17/0a272e6087ddb24bec96528acf061341845f458671e2a5cb35ff867a7c89/xmlsec-1.3.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6ac2154311d32a6571e22f224ed16356029e59bd5ca76edeb3922a809adfe89c", size = 3746315, upload-time = "2025-03-11T22:36:43.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/91/7ce9317e3a2a03e3811e62be52e091c1e661da2d59b5c7f60ec1840a1e6b/xmlsec-1.3.15-cp313-cp313-win32.whl", hash = "sha256:5ed218129f89b0592926ad2be42c017bece469db9b7380dc41bc09b01ca26d5d", size = 2146158, upload-time = "2025-03-11T22:36:44.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/e0/93311b9eedc11055ba667e666dc6ca1e2cc59c2356e91b73c3d5a6738fbf/xmlsec-1.3.15-cp313-cp313-win_amd64.whl", hash = "sha256:5fc29e69b064323317b3862751a3a8107670e0a17510ca4517bbdc1939a90b1a", size = 2442027, upload-time = "2025-03-11T22:36:46.431Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yarl"
|
||||
|
1
web/package-lock.json
generated
1
web/package-lock.json
generated
@ -7,7 +7,6 @@
|
||||
"": {
|
||||
"name": "@goauthentik/web",
|
||||
"version": "0.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
".",
|
||||
|
@ -19,7 +19,6 @@
|
||||
"lint:precommit": "wireit",
|
||||
"lint:types": "wireit",
|
||||
"lit-analyse": "wireit",
|
||||
"postinstall": "bash scripts/patch-spotlight.sh",
|
||||
"precommit": "wireit",
|
||||
"prettier": "wireit",
|
||||
"prettier-check": "wireit",
|
||||
|
@ -6,7 +6,7 @@
|
||||
* @import { Message as ESBuildMessage } from "esbuild";
|
||||
*/
|
||||
|
||||
const logPrefix = "👷 [ESBuild]";
|
||||
const logPrefix = "authentik/dev/web: ";
|
||||
const log = console.debug.bind(console, logPrefix);
|
||||
|
||||
/**
|
||||
@ -76,7 +76,7 @@ export class ESBuildObserver extends EventSource {
|
||||
*/
|
||||
#startListener = () => {
|
||||
this.#trackActivity();
|
||||
log("⏰ Build started...");
|
||||
log("⏰ Build started...");
|
||||
};
|
||||
|
||||
#internalErrorListener = () => {
|
||||
@ -86,7 +86,7 @@ export class ESBuildObserver extends EventSource {
|
||||
clearTimeout(this.#keepAliveInterval);
|
||||
|
||||
this.close();
|
||||
log("⛔️ Closing connection");
|
||||
log("⛔️ Closing connection");
|
||||
}
|
||||
};
|
||||
|
||||
@ -126,13 +126,13 @@ export class ESBuildObserver extends EventSource {
|
||||
this.#trackActivity();
|
||||
|
||||
if (!this.online) {
|
||||
log("🚫 Build finished while offline.");
|
||||
log("🚫 Build finished while offline.");
|
||||
this.deferredReload = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log("🛎️ Build completed! Reloading...");
|
||||
log("🛎️ Build completed! Reloading...");
|
||||
|
||||
// We use an animation frame to keep the reload from happening before the
|
||||
// event loop has a chance to process the message.
|
||||
@ -189,13 +189,13 @@ export class ESBuildObserver extends EventSource {
|
||||
|
||||
if (!this.deferredReload) return;
|
||||
|
||||
log("🛎️ Reloading after offline build...");
|
||||
log("🛎️ Reloading after offline build...");
|
||||
this.deferredReload = false;
|
||||
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
log("🛎️ Listening for build changes...");
|
||||
log("🛎️ Listening for build changes...");
|
||||
|
||||
this.#keepAliveInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
@ -203,7 +203,7 @@ export class ESBuildObserver extends EventSource {
|
||||
if (now - this.lastUpdatedAt < 10_000) return;
|
||||
|
||||
this.alive = false;
|
||||
log("👋 Waiting for build to start...");
|
||||
log("👋 Waiting for build to start...");
|
||||
}, 15_000);
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,16 @@ class SimpleFlowExecutor {
|
||||
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
|
||||
}
|
||||
|
||||
loading() {
|
||||
this.container.innerHTML = `<div class="d-flex justify-content-center">
|
||||
<div class="spinner-border spinner-border-md" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.loading();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: this.apiURL,
|
||||
@ -201,6 +210,9 @@ class PasswordStage extends Stage<PasswordChallenge> {
|
||||
<form id="password-form">
|
||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||
<div class="form-label-group my-3">
|
||||
<input type="text" readonly class="form-control-plaintext" value="Welcome, ${this.challenge?.pendingUser}.">
|
||||
</div>
|
||||
<div class="form-label-group my-3 has-validation">
|
||||
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
||||
${this.renderInputError("password")}
|
||||
|
@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TARGET="./node_modules/@spotlightjs/overlay/dist/index-"[0-9a-f]*.js
|
||||
|
||||
if [[ $(grep -L "QX2" "$TARGET" > /dev/null 2> /dev/null) ]]; then
|
||||
patch --forward -V none --no-backup-if-mismatch -p0 $TARGET <<EOF
|
||||
|
||||
TARGET=$(find "./node_modules/@spotlightjs/overlay/dist/" -name "index-[0-9a-f]*.js");
|
||||
|
||||
if ! grep -GL 'QX2 = ' "$TARGET" > /dev/null ; then
|
||||
patch --forward --no-backup-if-mismatch -p0 "$TARGET" <<EOF
|
||||
>>>>>>> main
|
||||
--- a/index-5682ce90.js 2024-06-13 16:19:28
|
||||
+++ b/index-5682ce90.js 2024-06-13 16:20:23
|
||||
@@ -4958,11 +4958,10 @@
|
||||
}
|
||||
);
|
||||
}
|
||||
-const q2 = w.lazy(() => import("./main-3257b7fc.js").then((n) => n.m));
|
||||
+const q2 = w.lazy(() => import("./main-3257b7fc.js").then((n) => n.m)), QX2 = () => {};
|
||||
function Gp({
|
||||
data: n,
|
||||
- onUpdateData: a = () => {
|
||||
- },
|
||||
+ onUpdateData: a = QX2,
|
||||
editingEnabled: s = !1,
|
||||
clipboardEnabled: o = !1,
|
||||
displayDataTypes: c = !1,
|
||||
EOF
|
||||
|
||||
else
|
||||
echo "spotlight overlay.js patch already applied"
|
||||
fi
|
@ -131,9 +131,9 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
//#region Lifecycle
|
||||
|
||||
constructor() {
|
||||
configureSentry(true);
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
|
||||
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||
}
|
||||
@ -167,7 +167,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
configureSentry(true);
|
||||
this.user = await me();
|
||||
|
||||
const canAccessAdmin =
|
||||
|
@ -113,8 +113,7 @@ export class ApplicationViewPage extends AKElement {
|
||||
|
||||
renderApp(): TemplateResult {
|
||||
if (!this.application) {
|
||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
||||
</ak-empty-state>`;
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
}
|
||||
return html`<ak-tabs>
|
||||
${this.missingOutpost
|
||||
|
@ -42,7 +42,7 @@ export class ProviderViewPage extends AKElement {
|
||||
|
||||
renderProvider(): TemplateResult {
|
||||
if (!this.provider) {
|
||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||
}
|
||||
switch (this.provider?.component) {
|
||||
case "ak-provider-saml-form":
|
||||
|
@ -432,7 +432,7 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
<div class="pf-c-card__body">
|
||||
${this.preview
|
||||
? html`<pre>${JSON.stringify(this.preview?.preview, null, 4)}</pre>`
|
||||
: html` <ak-empty-state ?loading=${true}></ak-empty-state> `}
|
||||
: html` <ak-empty-state loading></ak-empty-state> `}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
@ -502,7 +502,7 @@ export class SAMLProviderViewPage extends AKElement {
|
||||
|
||||
renderTabPreview(): TemplateResult {
|
||||
if (!this.preview) {
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
}
|
||||
return html` <div
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
|
||||
|
@ -34,7 +34,7 @@ export class SourceViewPage extends AKElement {
|
||||
|
||||
renderSource(): TemplateResult {
|
||||
if (!this.source) {
|
||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||
}
|
||||
switch (this.source?.component) {
|
||||
case "ak-source-kerberos-form":
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from "@goauthentik/common/api/middleware";
|
||||
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { SentryMiddleware } from "@goauthentik/common/sentry";
|
||||
|
||||
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
||||
|
||||
@ -66,21 +67,13 @@ export function brand(): Promise<CurrentBrand> {
|
||||
return globalBrandPromise;
|
||||
}
|
||||
|
||||
export function getMetaContent(key: string): string {
|
||||
const metaEl = document.querySelector<HTMLMetaElement>(`meta[name=${key}]`);
|
||||
if (!metaEl) return "";
|
||||
return metaEl.content;
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG = new Configuration({
|
||||
basePath: `${globalAK().api.base}api/v3`,
|
||||
headers: {
|
||||
"sentry-trace": getMetaContent("sentry-trace"),
|
||||
},
|
||||
middleware: [
|
||||
new CSRFMiddleware(),
|
||||
new EventMiddleware(),
|
||||
new LoggingMiddleware(globalAK().brand),
|
||||
new SentryMiddleware(),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { config } from "@goauthentik/common/api/config";
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||
import {
|
||||
@ -10,8 +10,16 @@ import {
|
||||
setTag,
|
||||
setUser,
|
||||
} from "@sentry/browser";
|
||||
import { getTraceData } from "@sentry/core";
|
||||
import * as Spotlight from "@spotlightjs/spotlight";
|
||||
|
||||
import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api";
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
FetchParams,
|
||||
Middleware,
|
||||
RequestContext,
|
||||
ResponseError,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
/**
|
||||
* A generic error that can be thrown without triggering Sentry's reporting.
|
||||
@ -21,69 +29,94 @@ export class SentryIgnoredError extends Error {}
|
||||
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
||||
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
||||
|
||||
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
||||
const cfg = await config();
|
||||
let _sentryConfigured = false;
|
||||
|
||||
if (cfg.errorReporting.enabled) {
|
||||
init({
|
||||
dsn: cfg.errorReporting.sentryDsn,
|
||||
ignoreErrors: [
|
||||
/network/gi,
|
||||
/fetch/gi,
|
||||
/module/gi,
|
||||
// Error on edge on ios,
|
||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||
// Seems to be an issue in Safari and Firefox
|
||||
/MutationObserver.observe/gi,
|
||||
/NS_ERROR_FAILURE/gi,
|
||||
],
|
||||
release: `authentik@${VERSION}`,
|
||||
export function configureSentry(canDoPpi = false) {
|
||||
const cfg = globalAK().config;
|
||||
const debug = cfg.capabilities.includes(CapabilitiesEnum.CanDebug);
|
||||
if (!cfg.errorReporting.enabled && !debug) {
|
||||
return cfg;
|
||||
}
|
||||
init({
|
||||
dsn: cfg.errorReporting.sentryDsn,
|
||||
ignoreErrors: [
|
||||
/network/gi,
|
||||
/fetch/gi,
|
||||
/module/gi,
|
||||
// Error on edge on ios,
|
||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||
// Seems to be an issue in Safari and Firefox
|
||||
/MutationObserver.observe/gi,
|
||||
/NS_ERROR_FAILURE/gi,
|
||||
],
|
||||
release: `authentik@${VERSION}`,
|
||||
integrations: [
|
||||
browserTracingIntegration({
|
||||
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
|
||||
instrumentNavigation: false,
|
||||
instrumentPageLoad: false,
|
||||
traceFetch: false,
|
||||
}),
|
||||
],
|
||||
tracePropagationTargets: [window.location.origin],
|
||||
tracesSampleRate: debug ? 1.0 : cfg.errorReporting.tracesSampleRate,
|
||||
environment: cfg.errorReporting.environment,
|
||||
beforeSend: (
|
||||
event: ErrorEvent,
|
||||
hint: EventHint,
|
||||
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
|
||||
if (!hint) {
|
||||
return event;
|
||||
}
|
||||
if (hint.originalException instanceof SentryIgnoredError) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
hint.originalException instanceof ResponseError ||
|
||||
hint.originalException instanceof DOMException
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
});
|
||||
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
||||
if (window.location.pathname.includes("if/")) {
|
||||
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
||||
}
|
||||
if (debug) {
|
||||
Spotlight.init({
|
||||
injectImmediately: true,
|
||||
integrations: [
|
||||
browserTracingIntegration({
|
||||
shouldCreateSpanForRequest: (url: string) => {
|
||||
return url.startsWith(window.location.host);
|
||||
},
|
||||
Spotlight.sentry({
|
||||
injectIntoSDK: true,
|
||||
}),
|
||||
],
|
||||
tracesSampleRate: cfg.errorReporting.tracesSampleRate,
|
||||
environment: cfg.errorReporting.environment,
|
||||
beforeSend: (
|
||||
event: ErrorEvent,
|
||||
hint: EventHint,
|
||||
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
|
||||
if (!hint) {
|
||||
return event;
|
||||
}
|
||||
if (hint.originalException instanceof SentryIgnoredError) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
hint.originalException instanceof ResponseError ||
|
||||
hint.originalException instanceof DOMException
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
});
|
||||
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
||||
if (window.location.pathname.includes("if/")) {
|
||||
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
||||
}
|
||||
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
const Spotlight = await import("@spotlightjs/spotlight");
|
||||
|
||||
Spotlight.init({ injectImmediately: true });
|
||||
}
|
||||
if (cfg.errorReporting.sendPii && canDoPpi) {
|
||||
me().then((user) => {
|
||||
setUser({ email: user.user.email });
|
||||
console.debug("authentik/config: Sentry with PII enabled.");
|
||||
});
|
||||
} else {
|
||||
console.debug("authentik/config: Sentry enabled.");
|
||||
}
|
||||
console.debug("authentik/config: Enabled Sentry Spotlight");
|
||||
}
|
||||
if (cfg.errorReporting.sendPii && canDoPpi) {
|
||||
me().then((user) => {
|
||||
setUser({ email: user.user.email });
|
||||
console.debug("authentik/config: Sentry with PII enabled.");
|
||||
});
|
||||
} else {
|
||||
console.debug("authentik/config: Sentry enabled.");
|
||||
}
|
||||
_sentryConfigured = true;
|
||||
}
|
||||
|
||||
export class SentryMiddleware implements Middleware {
|
||||
pre?(context: RequestContext): Promise<FetchParams | void> {
|
||||
if (!_sentryConfigured) {
|
||||
return Promise.resolve(context);
|
||||
}
|
||||
const traceData = getTraceData();
|
||||
// @ts-ignore
|
||||
context.init.headers["baggage"] = traceData["baggage"];
|
||||
// @ts-ignore
|
||||
context.init.headers["sentry-trace"] = traceData["sentry-trace"];
|
||||
return Promise.resolve(context);
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ export class Diagram extends AKElement {
|
||||
}
|
||||
});
|
||||
if (!this.diagram) {
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
}
|
||||
return html`${until(
|
||||
mermaid.render("graph", this.diagram).then((r) => {
|
||||
|
@ -23,9 +23,20 @@ const configContext = Symbol("configContext");
|
||||
const modalController = Symbol("modalController");
|
||||
const versionContext = Symbol("versionContext");
|
||||
|
||||
export abstract class Interface extends AKElement implements ThemedElement {
|
||||
export abstract class LightInterface extends AKElement implements ThemedElement {
|
||||
protected static readonly PFBaseStyleSheet = createStyleSheetUnsafe(PFBase);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const styleParent = resolveStyleSheetParent(document);
|
||||
|
||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||
|
||||
appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Interface extends LightInterface implements ThemedElement {
|
||||
[configContext]: ConfigContextController;
|
||||
|
||||
[modalController]: ModalOrchestrationController;
|
||||
@ -38,12 +49,6 @@ export abstract class Interface extends AKElement implements ThemedElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const styleParent = resolveStyleSheetParent(document);
|
||||
|
||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||
|
||||
appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
|
||||
|
||||
this.addController(new BrandContextController(this));
|
||||
this[configContext] = new ConfigContextController(this);
|
||||
this[modalController] = new ModalOrchestrationController(this);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AuthenticatedInterface, Interface } from "./Interface";
|
||||
import { AuthenticatedInterface, Interface, LightInterface } from "./Interface";
|
||||
|
||||
export { Interface, AuthenticatedInterface };
|
||||
export { Interface, AuthenticatedInterface, LightInterface };
|
||||
export default Interface;
|
||||
|
@ -230,9 +230,7 @@ export abstract class AKChart<T> extends AKElement {
|
||||
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
||||
</ak-empty-state>
|
||||
`
|
||||
: html`${this.chart
|
||||
? html``
|
||||
: html`<ak-empty-state ?loading="${true}"></ak-empty-state>`}`}
|
||||
: html`${this.chart ? html`` : html`<ak-empty-state loading></ak-empty-state>`}`}
|
||||
${this.centerText ? html` <span>${this.centerText}</span> ` : html``}
|
||||
<canvas style="${this.chart === undefined ? "display: none;" : ""}"></canvas>
|
||||
</div>
|
||||
|
@ -71,7 +71,7 @@ export abstract class ModelForm<T, PKT extends string | number> extends Form<T>
|
||||
|
||||
renderVisible(): TemplateResult {
|
||||
if ((this._instancePk && !this.instance) || !this._initialDataLoad) {
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
}
|
||||
return super.renderVisible();
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ export class Route {
|
||||
if (this.callback) {
|
||||
return html`${until(
|
||||
this.callback(args),
|
||||
html`<ak-empty-state ?loading=${true}></ak-empty-state>`,
|
||||
html`<ak-empty-state loading></ak-empty-state>`,
|
||||
)}`;
|
||||
}
|
||||
if (this.element) {
|
||||
|
@ -6,19 +6,35 @@ import { TemplateResult } from "lit";
|
||||
export class RouteMatch {
|
||||
route: Route;
|
||||
arguments: { [key: string]: string };
|
||||
fullUrl?: string;
|
||||
fullURL: string;
|
||||
|
||||
constructor(route: Route) {
|
||||
constructor(route: Route, fullUrl: string) {
|
||||
this.route = route;
|
||||
this.arguments = {};
|
||||
this.fullURL = fullUrl;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return this.route.render(this.arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the matched Route's URL regex to a sanitized, readable URL by replacing
|
||||
* all regex values with placeholders according to the name of their regex group.
|
||||
*
|
||||
* @returns The sanitized URL for logging/tracing.
|
||||
*/
|
||||
sanitizedURL() {
|
||||
let cleanedURL = this.fullURL;
|
||||
for (const match of Object.keys(this.arguments)) {
|
||||
const value = this.arguments[match];
|
||||
cleanedURL = cleanedURL?.replace(value, `:${match}`);
|
||||
}
|
||||
return cleanedURL;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `<RouteMatch url=${this.fullUrl} route=${this.route} arguments=${JSON.stringify(
|
||||
return `<RouteMatch url=${this.sanitizedURL()} route=${this.route} arguments=${JSON.stringify(
|
||||
this.arguments,
|
||||
)}>`;
|
||||
}
|
||||
|
@ -3,8 +3,15 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { Route } from "@goauthentik/elements/router/Route";
|
||||
import { RouteMatch } from "@goauthentik/elements/router/RouteMatch";
|
||||
import "@goauthentik/elements/router/Router404";
|
||||
import {
|
||||
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
|
||||
getClient,
|
||||
startBrowserTracingNavigationSpan,
|
||||
startBrowserTracingPageLoadSpan,
|
||||
} from "@sentry/browser";
|
||||
import { Client, Span } from "@sentry/types";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { CSSResult, PropertyValues, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
// Poliyfill for hashchange.newURL,
|
||||
@ -53,6 +60,9 @@ export class RouterOutlet extends AKElement {
|
||||
@property({ attribute: false })
|
||||
routes: Route[] = [];
|
||||
|
||||
private sentryClient?: Client;
|
||||
private pageLoadSpan?: Span;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
@ -69,6 +79,15 @@ export class RouterOutlet extends AKElement {
|
||||
constructor() {
|
||||
super();
|
||||
window.addEventListener("hashchange", (ev: HashChangeEvent) => this.navigate(ev));
|
||||
this.sentryClient = getClient();
|
||||
if (this.sentryClient) {
|
||||
this.pageLoadSpan = startBrowserTracingPageLoadSpan(this.sentryClient, {
|
||||
name: window.location.pathname,
|
||||
attributes: {
|
||||
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
@ -92,9 +111,8 @@ export class RouterOutlet extends AKElement {
|
||||
this.routes.some((route) => {
|
||||
const match = route.url.exec(activeUrl);
|
||||
if (match !== null) {
|
||||
matchedRoute = new RouteMatch(route);
|
||||
matchedRoute = new RouteMatch(route, activeUrl);
|
||||
matchedRoute.arguments = match.groups || {};
|
||||
matchedRoute.fullUrl = activeUrl;
|
||||
console.debug("authentik/router: found match ", matchedRoute);
|
||||
return true;
|
||||
}
|
||||
@ -107,13 +125,31 @@ export class RouterOutlet extends AKElement {
|
||||
<ak-router-404 url=${activeUrl}></ak-router-404>
|
||||
</div>`;
|
||||
});
|
||||
matchedRoute = new RouteMatch(route);
|
||||
matchedRoute = new RouteMatch(route, activeUrl);
|
||||
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
|
||||
matchedRoute.fullUrl = activeUrl;
|
||||
}
|
||||
this.current = matchedRoute;
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>): void {
|
||||
if (!changedProperties.has("current") || !this.current) return;
|
||||
if (!this.sentryClient) return;
|
||||
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
|
||||
if (this.pageLoadSpan) {
|
||||
this.pageLoadSpan.updateName(this.current.sanitizedURL());
|
||||
this.pageLoadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "route");
|
||||
this.pageLoadSpan = undefined;
|
||||
} else {
|
||||
startBrowserTracingNavigationSpan(this.sentryClient, {
|
||||
op: "navigation",
|
||||
name: this.current.sanitizedURL(),
|
||||
attributes: {
|
||||
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render(): TemplateResult | undefined {
|
||||
return this.current?.render();
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ export class SyncStatusCard extends AKElement {
|
||||
|
||||
renderSyncStatus(): TemplateResult {
|
||||
if (this.loading) {
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
}
|
||||
if (!this.syncState) {
|
||||
return html`${msg("No sync status.")}`;
|
||||
|
@ -19,7 +19,7 @@ describe("ak-empty-state", () => {
|
||||
});
|
||||
|
||||
it("should render the default loader", async () => {
|
||||
render(html`<ak-empty-state ?loading=${true} header=${msg("Loading")}> </ak-empty-state>`);
|
||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
@ -139,8 +139,7 @@ export class UserSourceSettingsPage extends AKElement {
|
||||
})}
|
||||
`}
|
||||
`
|
||||
: html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
||||
</ak-empty-state>`}
|
||||
: html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`}
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
|
@ -171,6 +171,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
|
||||
constructor() {
|
||||
configureSentry();
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
const inspector = new URL(window.location.toString()).searchParams.get("inspector");
|
||||
@ -237,7 +238,6 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
configureSentry();
|
||||
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
this.inspectorAvailable = true;
|
||||
}
|
||||
|
@ -24,8 +24,7 @@ export class SessionEnd extends BaseStage<SessionEndChallenge, unknown> {
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.challenge) {
|
||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
||||
</ak-empty-state>`;
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
}
|
||||
return html`<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
import { LightInterface } from "@goauthentik/elements/Interface";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
@ -10,7 +10,7 @@ import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-loading")
|
||||
export class Loading extends Interface {
|
||||
export class Loading extends LightInterface {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
@ -25,16 +25,6 @@ export class Loading extends Interface {
|
||||
];
|
||||
}
|
||||
|
||||
registerContexts(): void {
|
||||
// Stub function to avoid making API requests for things we don't need. The `Interface` base class loads
|
||||
// a bunch of data that is used globally by various things, however this is an interface that is shown
|
||||
// very briefly and we don't need any of that data.
|
||||
}
|
||||
|
||||
async _initCustomCSS(): Promise<void> {
|
||||
// Stub function to avoid fetching custom CSS.
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <section
|
||||
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
||||
|
@ -102,7 +102,7 @@ export class LibraryPage extends AKElement {
|
||||
}
|
||||
|
||||
loading() {
|
||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> </ak-empty-state>`;
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
}
|
||||
|
||||
running() {
|
||||
|
@ -281,10 +281,10 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
me?: SessionUser;
|
||||
|
||||
constructor() {
|
||||
configureSentry(true);
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
this.fetchConfigurationDetails();
|
||||
configureSentry(true);
|
||||
this.toggleNotificationDrawer = this.toggleNotificationDrawer.bind(this);
|
||||
this.toggleApiDrawer = this.toggleApiDrawer.bind(this);
|
||||
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
|
||||
|
@ -173,8 +173,7 @@ export class UserSettingsFlowExecutor
|
||||
level: MessageLevel.success,
|
||||
message: msg("Successfully updated details"),
|
||||
});
|
||||
return html`<ak-empty-state ?loading=${true} header=${msg("Loading")}>
|
||||
</ak-empty-state>`;
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
default:
|
||||
console.debug(
|
||||
`authentik/user/flows: unsupported stage type ${this.challenge.component}`,
|
||||
@ -195,8 +194,7 @@ export class UserSettingsFlowExecutor
|
||||
return html`<p>${msg("No settings flow configured.")}</p> `;
|
||||
}
|
||||
if (!this.challenge || this.loading) {
|
||||
return html`<ak-empty-state ?loading=${true} header=${msg("Loading")}>
|
||||
</ak-empty-state>`;
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
}
|
||||
return html` ${this.renderChallenge()} `;
|
||||
}
|
||||
|
@ -64,8 +64,7 @@ export class UserSettingsPromptStage extends PromptStage {
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.challenge) {
|
||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
||||
</ak-empty-state>`;
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
}
|
||||
return html`<div class="pf-c-login__main-body">
|
||||
<form
|
||||
|
@ -88,4 +88,4 @@ import Defaultflowlist from "../flow/flow_list/\_defaultflowlist.mdx";
|
||||
|
||||
- **Layout**: select how the UI displays the flow when it is executed; with stacked elements, content left or right, and sidebar left or right.
|
||||
|
||||
- **Background**: optionally, select a background image for the UI presentation of the flow.
|
||||
- **Background**: optionally, select a background image for the UI presentation of the flow. This overrides any default background image configured in the [Branding settings](../../../sys-mgmt/brands.md#branding-settings).
|
||||
|
@ -57,7 +57,7 @@ To bind a stage to a flow, follow these steps:
|
||||
|
||||
## Bind users and groups to a flow's stage binding
|
||||
|
||||
You can use bindings to determine whehther or not a stage is presented to a single user or any users within a group. You do this by binding the user or group to a stage binding within a specific flow. For example, if you have a flow that contains a stage that prompts the user for multi-factor authentication, but you only want certain users to see this stage (and fulfill the MFA prompt), then you would bind the appropriate group (or single user) to the stage binding for that flow.
|
||||
You can use bindings to determine whether or not a stage is presented to a single user or any users within a group. You do this by binding the user or group to a stage binding within a specific flow. For example, if you have a flow that contains a stage that prompts the user for multi-factor authentication, but you only want certain users to see this stage (and fulfill the MFA prompt), then you would bind the appropriate group (or single user) to the stage binding for that flow.
|
||||
|
||||
To bind a user or a group to a stage binding for a specific flow, follow these steps:
|
||||
|
||||
|
@ -1,57 +0,0 @@
|
||||
### Custom CSS
|
||||
|
||||
To further modify the look of authentik, a custom CSS file can be created. Creating such a file is outside the scope of this document.
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker-compose"
|
||||
values={[
|
||||
{label: 'docker-compose', value: 'docker-compose'},
|
||||
{label: 'Kubernetes', value: 'kubernetes'},
|
||||
]}>
|
||||
<TabItem value="docker-compose">
|
||||
Create a `docker-compose.override.yml` file and add this block to mount the custom CSS file:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
server:
|
||||
volumes:
|
||||
- ./my-css-file.css:/web/dist/custom.css
|
||||
```
|
||||
|
||||
Afterwards, run the upgrade commands from the latest release notes.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="kubernetes">
|
||||
Create a ConfigMap with your css file:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-custom-css
|
||||
namespace: authentik
|
||||
data:
|
||||
custom.css: |
|
||||
...
|
||||
```
|
||||
|
||||
Then, in the helm chart add this to your `values.yaml` file:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- name: custom-css
|
||||
configMap:
|
||||
name: authentik-custom-css
|
||||
volumeMounts:
|
||||
- name: custom-css
|
||||
mountPath: /web/dist/custom.css
|
||||
subPath: custom.css
|
||||
```
|
||||
|
||||
Afterwards, run the upgrade commands from the latest release notes.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
@ -1,5 +1,3 @@
|
||||
## Global customization
|
||||
### Global customization
|
||||
|
||||
import CustomCSS from "./customcss.mdx";
|
||||
|
||||
<CustomCSS />
|
||||
See [Brand Settings](../../../sys-mgmt/brands.md#branding-settings)
|
||||
|
@ -29,9 +29,10 @@ authentik:
|
||||
To store the password and token in a secret, use:
|
||||
|
||||
```yaml
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: _some-secret_
|
||||
global:
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: _some-secret_
|
||||
```
|
||||
|
||||
where _some-secret_ contains the environment variables as in the documentation above.
|
||||
|
@ -70,7 +70,8 @@ To check if your config has been applied correctly, you can run the following co
|
||||
- `AUTHENTIK_POSTGRESQL__USER`: Database user
|
||||
- `AUTHENTIK_POSTGRESQL__PORT`: Database port, defaults to 5432
|
||||
- `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD`
|
||||
- `AUTHENTIK_POSTGRESQL__USE_POOL`: Use a [connection pool](https://docs.djangoproject.com/en/stable/ref/databases/#connection-pool) for PostgreSQL connections. Defaults to `false`. :ak-version[2025.4]
|
||||
{/* TODO: Temporarily deactivated feature, see https://github.com/goauthentik/authentik/issues/14320 */}
|
||||
{/* - `AUTHENTIK_POSTGRESQL__USE_POOL`: Use a [connection pool](https://docs.djangoproject.com/en/stable/ref/databases/#connection-pool) for PostgreSQL connections. Defaults to `false`. :ak-version[2025.4] */}
|
||||
- `AUTHENTIK_POSTGRESQL__POOL_OPTIONS`: Extra configuration to pass to the [ConnectionPool object](https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.ConnectionPool) when it is created. Must be a base64-encoded JSON dictionary. Ignored when `USE_POOL` is set to `false`. :ak-version[2025.4]
|
||||
- `AUTHENTIK_POSTGRESQL__USE_PGBOUNCER`: Adjust configuration to support connection to PgBouncer. Deprecated, see below
|
||||
- `AUTHENTIK_POSTGRESQL__USE_PGPOOL`: Adjust configuration to support connection to Pgpool. Deprecated, see below
|
||||
@ -356,7 +357,7 @@ Defaults to `86400`.
|
||||
|
||||
### `AUTHENTIK_SESSION_STORAGE`:ak-version[2024.4]
|
||||
|
||||
Configure if the sessions are stored in the cache or the database. Defaults to `cache`. Allowed values are `cache` and `db`. Note that changing this value will invalidate all previous sessions.
|
||||
Configure if the sessions are stored in the cache or the database. Defaults to `db`. Allowed values are `cache` and `db`. Note that changing this value will invalidate all previous sessions.
|
||||
|
||||
### `AUTHENTIK_SESSIONS__UNAUTHENTICATED_AGE`:ak-version[2025.4]
|
||||
|
||||
|
@ -7,7 +7,8 @@ slug: "/releases/2025.4"
|
||||
|
||||
- **Improve membership resolution for the LDAP Source** Allow lookups of LDAP group memberships from user attributes as an alternative to lookups from group attributes. This also allows for nested group lookups in Active Directory.
|
||||
|
||||
- **Support for PostgreSQL Connection Pools** PostgreSQL Connection Pools provides a set of open connections in order to reduce latency.
|
||||
<!-- TODO: Temporarily deactivated feature, see https://github.com/goauthentik/authentik/issues/14320 -->
|
||||
<!-- - **Support for PostgreSQL Connection Pools** PostgreSQL Connection Pools provides a set of open connections in order to reduce latency. -->
|
||||
|
||||
- **RBAC: Initial Permissions** :ak-preview Provides more flexible access control by assigning permissions to the user/role creating a new object in authentik. Use **Initial Permissions** as a pragmatic way to implement the principle of least privilege.
|
||||
|
||||
@ -64,7 +65,7 @@ Previously, sessions were stored by default in the cache. Now, they are stored i
|
||||
|
||||
Reputation scores now have a configurable numerical limit in addition to the [already existing temporal limit](https://docs.goauthentik.io/docs/install-config/configuration/#authentik_reputation_expiry).
|
||||
|
||||
- **Support for PostgreSQL Connection Pools**: See [description](#highlights) under Highlights. Refer to our [documentation](../../install-config/configuration/configuration.mdx).
|
||||
<!-- - **Support for PostgreSQL Connection Pools**: See [description](#highlights) under Highlights. Refer to our [documentation](../../install-config/configuration/configuration.mdx). -->
|
||||
|
||||
- **Password History Policy**: See [description](#highlights) under Highlights. Refer to our [documentation](../../customize/policies/unique_password.md).
|
||||
|
||||
|
@ -23,11 +23,14 @@ The brand settings define the visual identity of the brand, including:
|
||||
|
||||
- **Branding title**: Displayed in the browser tab (document title) and throughout the UI;
|
||||
- **Logo**: Appears in the sidebar/header;
|
||||
- **Favicon**: Shown on the browser tab.
|
||||
|
||||
:::info
|
||||
Starting with authentik 2024.6.2, the placeholder `%(theme)s` can be used in the logo configuration option, which will be replaced with the active theme.
|
||||
:::
|
||||
:::info
|
||||
Starting with authentik 2024.6.2, the placeholder `%(theme)s` can be used in the logo configuration option, which will be replaced with the active theme.
|
||||
:::
|
||||
|
||||
- **Favicon**: Shown on the browser tab.
|
||||
- **Default flow background** :ak-version[2025.4]: Default background image for the flow executor, can be overridden per flow, see [Flow configuration options](../add-secure-apps/flows-stages/flow/index.md#flow-configuration-options).
|
||||
- **Custom CSS** :ak-version[2025.4]: Add custom CSS to further customize the look of authentik. Creating such a file is outside the scope of this document.
|
||||
|
||||
### External user settings
|
||||
|
||||
|
@ -4,12 +4,9 @@
|
||||
* @import * as Preset from "@docusaurus/preset-classic";
|
||||
* @import * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
||||
* @import { BuildUrlValues } from "remark-github";
|
||||
* @import { ReleasesPluginOptions } from "./releases/plugin.mjs"
|
||||
*/
|
||||
import { createDocusaurusConfig } from "@goauthentik/docusaurus-config";
|
||||
import { createRequire } from "node:module";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import remarkDirective from "remark-directive";
|
||||
import remarkGithub, { defaultBuildUrl } from "remark-github";
|
||||
|
||||
@ -18,7 +15,6 @@ import remarkPreviewDirective from "./remark/preview-directive.mjs";
|
||||
import remarkSupportDirective from "./remark/support-directive.mjs";
|
||||
import remarkVersionDirective from "./remark/version-directive.mjs";
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
@ -135,12 +131,6 @@ const config = createDocusaurusConfig({
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"./releases/plugin.mjs",
|
||||
/** @type {ReleasesPluginOptions} */ ({
|
||||
docsDirectory: path.join(__dirname, "docs"),
|
||||
}),
|
||||
],
|
||||
[
|
||||
"@docusaurus/plugin-content-docs",
|
||||
{
|
||||
|
62
website/integrations/services/coder/index.md
Normal file
62
website/integrations/services/coder/index.md
Normal file
@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Integrate with Coder
|
||||
sidebar_label: Coder
|
||||
support_level: community
|
||||
---
|
||||
|
||||
## What is Coder
|
||||
|
||||
> Coder is an open-source platform that provides browser-based cloud development environments, enabling developers and teams to securely write, edit, and manage code remotely without the need for local setup.
|
||||
>
|
||||
> -- https://coder.com
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders are used in this guide:
|
||||
|
||||
- `coder.company` is the FQDN of your Coder installation.
|
||||
- `authentik.company` is the FQDN of the authentik installation.
|
||||
|
||||
:::note
|
||||
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
|
||||
:::
|
||||
|
||||
## authentik configuration
|
||||
|
||||
To support the integration of Coder with authentik, you need to create an application/provider pair in authentik.
|
||||
|
||||
### Create an application and provider in authentik
|
||||
|
||||
1. Log in to authentik as an admin, and open the authentik Admin interface.
|
||||
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
|
||||
|
||||
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- Note the **Client ID**,**Client Secret**, and **slug** values because they will be required later.
|
||||
- Set a `Strict` redirect URI to `https://coder.company/api/v2/users/oidc/callback`.
|
||||
- Select any available signing key.
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
|
||||
|
||||
3. Click **Submit** to save the new application and provider.
|
||||
|
||||
## Coder configuration
|
||||
|
||||
To support the integration of Coder with authentik, add the following environment variables to your Coder deployment:
|
||||
|
||||
```yaml showLineNumbers
|
||||
CODER_OIDC_ISSUER_URL=https://authentik.company/application/o/<application slug>/
|
||||
CODER_OIDC_EMAIL_DOMAIN=acme.company,acme-corp.company
|
||||
CODER_OIDC_CLIENT_ID=<Client ID from authentik>
|
||||
CODER_OIDC_CLIENT_SECRET=<Client secret from authentik>
|
||||
CODER_OIDC_SIGN_IN_TEXT=Log in with authentik
|
||||
CODER_OIDC_ICON_URL=https://authentik.company/static/dist/assets/icons/icon.png
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Coder OIDC authentication documentatiom](https://coder.com/docs/admin/users/oidc-auth/)
|
||||
|
||||
## Configuration verification
|
||||
|
||||
To confirm that authentik is properly configured with Coder, log out and attempt to log back in by clicking **Log in with authentik**.
|
@ -129,6 +129,8 @@ environment:
|
||||
GF_AUTH_OAUTH_AUTO_LOGIN: "true"
|
||||
# Optionally map user groups to Grafana roles
|
||||
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "contains(groups, 'Grafana Admins') && 'Admin' || contains(groups, 'Grafana Editors') && 'Editor' || 'Viewer'"
|
||||
# Required if Grafana is running behind a reverse proxy
|
||||
GF_SERVER_ROOT_URL: "https://grafana.company"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@ -194,7 +196,8 @@ For more information on group/role mappings, see [Grafana's docs](https://grafan
|
||||
|
||||
### Grafana Configuration Considerations
|
||||
|
||||
Make sure in your configuration that `root_url` is set correctly, otherwise your redirect url might get processed incorrectly. For example, if your grafana instance is running on the default configuration and is accessible behind a reverse proxy at `https://grafana.company`, your redirect url will end up looking like this, `https://grafana.company/`.
|
||||
To ensure redirects work correctly in Grafana, make sure the `root_url` in your configuration accurately reflects how users access Grafana through your reverse proxy. For example, if your Grafana instance is behind a proxy and accessed at `https://grafana.company`, set `root_url` to `https://grafana.company`. This ensures that OAuth and other redirects use the correct URL, such as `https://grafana.company/login/generic_oauth`, instead of defaulting to something like `localhost:3000`.
|
||||
|
||||
If you get `user does not belong to org` error when trying to log into grafana for the first time via OAuth, check if you have an organization with the ID of `1`, if not, then you have to add the following to your grafana config:
|
||||
|
||||
```ini
|
||||
|
@ -30,12 +30,14 @@ To support the integration of Paperless-ngx with authentik, you need to create a
|
||||
1. Log in to authentik as an admin, and open the authentik Admin interface.
|
||||
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
|
||||
|
||||
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- Note the **Client ID**,**Client Secret**, and **slug** values because they will be required later.
|
||||
- Set a `Strict` redirect URI to <kbd>https://<em>paperless.company</em>/accounts/oidc/authentik/login/callback/</kbd>.
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
|
||||
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- Note the **Client ID**,**Client Secret**, and **slug** values because they will be required later.
|
||||
- Set a `Strict` redirect URI to <kbd>https://<em>paperless.company</em>/accounts/oidc/authentik/login/callback/</kbd>.
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
|
||||
- **Advanced protocol settings**:
|
||||
- **Selected Scopes**: Additionally select the `authentik default OAuth Mapping: OpenID 'openid'` scope.
|
||||
|
||||
3. Click **Submit** to save the new application and provider.
|
||||
|
||||
|
@ -61,6 +61,7 @@ Add the `oidc_providers` configuration:
|
||||
"name_claim": "name",
|
||||
"email_claim": "email",
|
||||
"scopes": ["openid", "profile", "email"]
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
---
|
||||
title: Integrate with SonarQube
|
||||
sidebar_label: SonarQube
|
||||
support_level: community
|
||||
---
|
||||
|
||||
<span className="badge badge--primary">Support level: Community</span>
|
||||
|
||||
## What is SonarQube
|
||||
|
||||
> Self-managed static analysis tool for continuous codebase inspection
|
||||
|
@ -6,7 +6,7 @@ support_level: community
|
||||
|
||||
## What is Service-Name
|
||||
|
||||
> Insert a quick overview of what Service Name is and what it does. Simply describe the product and what it is, how it is used, and do not include marketing or sales-oriented content.
|
||||
> Insert a quick overview of what `<service-name>` is and what it does. Simply describe the product and what it is, how it is used, and do not include marketing or sales-oriented content.
|
||||
>
|
||||
> -- https://service.xyz
|
||||
|
||||
@ -14,7 +14,7 @@ support_level: community
|
||||
|
||||
The following placeholders are used in this guide:
|
||||
|
||||
- `service.company` is the FQDN of the Service installation. (Remove this for SaaS)
|
||||
- `service.company` is the FQDN of the `<service-name>` installation. (Remove this for SaaS)
|
||||
- `authentik.company` is the FQDN of the authentik installation.
|
||||
|
||||
:::note
|
||||
@ -23,7 +23,7 @@ This documentation lists only the settings that you need to change from their de
|
||||
|
||||
## authentik configuration
|
||||
|
||||
To support the integration of _Service_ with authentik, you need to create an application/provider pair in authentik.
|
||||
To support the integration of `<service-name>` with authentik, you need to create an application/provider pair in authentik.
|
||||
|
||||
_Any specific info about this integration can go here._
|
||||
|
||||
@ -32,13 +32,12 @@ _Any specific info about this integration can go here._
|
||||
1. Log in to authentik as an admin, and open the authentik Admin interface.
|
||||
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
|
||||
|
||||
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- _If there are any specific settings required, list them here. Refer to the [ownCloud integration documentation](https://github.com/goauthentik/authentik/blob/main/website/integrations/services/owncloud/index.md) for a complex requirements example._
|
||||
- **Choose a Provider type**
|
||||
- _If there is a specific provider type required, state that here._
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- _If there are any specific settings required, list them here. Refer to the [ownCloud integration documentation](https://github.com/goauthentik/authentik/blob/main/website/integrations/services/owncloud/index.md) for a complex requirements example._
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
|
||||
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- _If there are any specific settings required, list them here. Refer to the [ownCloud integration documentation](https://github.com/goauthentik/authentik/blob/main/website/integrations/services/owncloud/index.md) for a complex requirements example._
|
||||
- **Choose a Provider type**: _If there is a specific provider type required, state that here._
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- _If there are any specific settings required, list them here. Refer to the [ownCloud integration documentation](https://github.com/goauthentik/authentik/blob/main/website/integrations/services/owncloud/index.md) for a complex requirements example._
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
|
||||
|
||||
3. Click **Submit** to save the new application and provider.
|
||||
|
||||
@ -52,6 +51,6 @@ Insert Service configuration
|
||||
|
||||
## Configuration verification
|
||||
|
||||
Template sentence that you can typically use here: "To confirm that authentik is properly configured with _Service Name_, log out and log back in via authentik."
|
||||
Template sentence that you can typically use here: "To confirm that authentik is properly configured with `<service-name>`, log out and log back in via authentik."
|
||||
|
||||
If there are more specific validation methods for the Service (e.g., clicking a button), include these instructions for clarity.
|
||||
|
57
website/package-lock.json
generated
57
website/package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"docusaurus-plugin-openapi-docs": "^4.4.0",
|
||||
"docusaurus-theme-openapi-docs": "^4.4.0",
|
||||
"lightningcss-linux-x64-gnu": "1.30.1",
|
||||
"postcss": "^8.5.3",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
@ -29,7 +30,7 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"remark-directive": "^4.0.0",
|
||||
"remark-github": "^12.0.0",
|
||||
"semver": "^7.7.1"
|
||||
"semver": "^7.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.7.0",
|
||||
@ -50,18 +51,18 @@
|
||||
"node": ">=22.14.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rspack/binding-darwin-arm64": "1.3.8",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.8",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.8",
|
||||
"@rspack/binding-darwin-arm64": "1.3.10",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.10",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.10",
|
||||
"@swc/core-darwin-arm64": "1.11.24",
|
||||
"@swc/core-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/core-linux-x64-gnu": "1.11.24",
|
||||
"@swc/html-darwin-arm64": "1.11.24",
|
||||
"@swc/html-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/html-linux-x64-gnu": "1.11.24",
|
||||
"lightningcss-darwin-arm64": "1.29.3",
|
||||
"lightningcss-linux-arm64-gnu": "1.29.3",
|
||||
"lightningcss-linux-x64-gnu": "1.29.3"
|
||||
"lightningcss-darwin-arm64": "1.30.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||
"lightningcss-linux-x64-gnu": "1.30.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@algolia/autocomplete-core": {
|
||||
@ -4675,9 +4676,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspack/binding-darwin-arm64": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.8.tgz",
|
||||
"integrity": "sha512-FlfWZzwCxDfLwyiqGaCSINHt2Er1Wno9xZrf2QM7Ss00HyocPo4BUYGYBEi4dai/fPFoeYKeEAdsNdrVmFH4+g==",
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.10.tgz",
|
||||
"integrity": "sha512-0k/j8OeMSVm5u5Nzckp9Ie7S7hprnvNegebnGr+L6VCyD7sMqm4m+4rLHs99ZklYdH0dZtY2+LrzrtjUZCqfew==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -4702,9 +4703,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-arm64-gnu": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.8.tgz",
|
||||
"integrity": "sha512-PU9fv8knPvbxQb8NrDmTrLVpy8QY0vuhzk69/ZuLRW89c0P14HovYeHV+38cQHho4++avUQgVp6vnJI9vSQjtg==",
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.10.tgz",
|
||||
"integrity": "sha512-zhF5ZNaT/7pxrm8xD3dWG1b4x+FO3LbVeZZG448CjpSo5T57kPD+SaGUU1GcPpn6mexf795x0SVS49aH7/e3Dg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -4729,9 +4730,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-x64-gnu": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.8.tgz",
|
||||
"integrity": "sha512-48hfwVsD2/Caa0HgZiqE1T20H89cnomcaP92++x8t4IQ2uKA9xCeBW87RD/AaKXcb78aM987ctE+asKjN8OVjw==",
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.10.tgz",
|
||||
"integrity": "sha512-FMSi28VZhXMr15picOHFynULhqZ/FODPxRIS6uNrvPRYcbNuiO1v+VHV6X88mhOMmJ/aVF6OwjUO/o2l1FVa9Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -14468,9 +14469,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.29.3",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.3.tgz",
|
||||
"integrity": "sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==",
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
|
||||
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -14548,9 +14549,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.29.3",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.3.tgz",
|
||||
"integrity": "sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==",
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
|
||||
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -14588,9 +14589,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.29.3",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.3.tgz",
|
||||
"integrity": "sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==",
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
|
||||
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -22637,9 +22638,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
@ -42,7 +42,7 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"remark-directive": "^4.0.0",
|
||||
"remark-github": "^12.0.0",
|
||||
"semver": "^7.7.1"
|
||||
"semver": "^7.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.7.0",
|
||||
@ -60,18 +60,18 @@
|
||||
"typescript": "~5.8.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rspack/binding-darwin-arm64": "1.3.8",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.8",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.8",
|
||||
"@rspack/binding-darwin-arm64": "1.3.10",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.10",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.10",
|
||||
"@swc/core-darwin-arm64": "1.11.24",
|
||||
"@swc/core-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/core-linux-x64-gnu": "1.11.24",
|
||||
"@swc/html-darwin-arm64": "1.11.24",
|
||||
"@swc/html-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/html-linux-x64-gnu": "1.11.24",
|
||||
"lightningcss-darwin-arm64": "1.29.3",
|
||||
"lightningcss-linux-arm64-gnu": "1.29.3",
|
||||
"lightningcss-linux-x64-gnu": "1.29.3"
|
||||
"lightningcss-darwin-arm64": "1.30.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||
"lightningcss-linux-x64-gnu": "1.30.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.14.0"
|
||||
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* @file Docusaurus releases plugin.
|
||||
*
|
||||
* @import { LoadContext, Plugin } from "@docusaurus/types"
|
||||
*/
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { collectReleaseFiles } from "./utils.mjs";
|
||||
|
||||
const PLUGIN_NAME = "ak-releases-plugin";
|
||||
const RELEASES_FILENAME = "releases.gen.json";
|
||||
|
||||
/**
|
||||
* @typedef {object} ReleasesPluginOptions
|
||||
* @property {string} docsDirectory The path to the documentation directory.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} AKReleasesPluginData
|
||||
* @property {string} publicPath The URL to the plugin's public directory.
|
||||
* @property {string[]} releases The available versions of the documentation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {LoadContext} loadContext
|
||||
* @param {ReleasesPluginOptions} options
|
||||
* @returns {Promise<Plugin<AKReleasesPluginData>>}
|
||||
*/
|
||||
async function akReleasesPlugin(loadContext, { docsDirectory }) {
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
|
||||
async loadContent() {
|
||||
console.log(`🚀 ${PLUGIN_NAME} loaded`);
|
||||
|
||||
const releases = collectReleaseFiles(docsDirectory).map((release) => release.name);
|
||||
|
||||
const outputPath = path.join(loadContext.siteDir, "static", RELEASES_FILENAME);
|
||||
|
||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||
await fs.writeFile(outputPath, JSON.stringify(releases, null, 2), "utf-8");
|
||||
console.log(`✅ ${RELEASES_FILENAME} generated`);
|
||||
|
||||
/**
|
||||
* @type {AKReleasesPluginData}
|
||||
*/
|
||||
const content = {
|
||||
releases,
|
||||
publicPath: path.join("/", RELEASES_FILENAME),
|
||||
};
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
contentLoaded({ content, actions }) {
|
||||
const { setGlobalData } = actions;
|
||||
|
||||
setGlobalData(content);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default akReleasesPlugin;
|
@ -1,69 +0,0 @@
|
||||
/**
|
||||
* @file Docusaurus release utils.
|
||||
*
|
||||
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs-types"
|
||||
*/
|
||||
import FastGlob from "fast-glob";
|
||||
import * as path from "node:path";
|
||||
import { coerce } from "semver";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} releasesParentDirectory
|
||||
* @returns {FastGlob.Entry[]}
|
||||
*/
|
||||
export function collectReleaseFiles(releasesParentDirectory) {
|
||||
const releaseFiles = FastGlob.sync("releases/**/v*.{md,mdx}", {
|
||||
cwd: releasesParentDirectory,
|
||||
onlyFiles: true,
|
||||
objectMode: true,
|
||||
})
|
||||
.map((fileEntry) => {
|
||||
return {
|
||||
...fileEntry,
|
||||
path: fileEntry.path.replace(/\.mdx?$/, ""),
|
||||
name: fileEntry.name.replace(/^v/, "").replace(/\.mdx?$/, ""),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aSemVer = coerce(a.name);
|
||||
const bSemVer = coerce(b.name);
|
||||
|
||||
if (aSemVer && bSemVer) {
|
||||
return bSemVer.compare(aSemVer);
|
||||
}
|
||||
|
||||
return b.name.localeCompare(a.name);
|
||||
});
|
||||
|
||||
return releaseFiles;
|
||||
}
|
||||
|
||||
export const SUPPORTED_RELEASE_COUNT = 3;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {FastGlob.Entry[]} releaseFiles
|
||||
*/
|
||||
export function createReleaseSidebarEntries(releaseFiles) {
|
||||
/**
|
||||
* @type {SidebarItemConfig[]}
|
||||
*/
|
||||
let sidebarEntries = releaseFiles.map((fileEntry) => {
|
||||
return path.join(fileEntry.path);
|
||||
});
|
||||
|
||||
if (releaseFiles.length > SUPPORTED_RELEASE_COUNT) {
|
||||
// Then we add the rest of the releases as a category.
|
||||
sidebarEntries = [
|
||||
...sidebarEntries.slice(0, SUPPORTED_RELEASE_COUNT),
|
||||
{
|
||||
type: "category",
|
||||
label: "Previous versions",
|
||||
items: sidebarEntries.slice(SUPPORTED_RELEASE_COUNT),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return sidebarEntries;
|
||||
}
|
@ -3,20 +3,73 @@
|
||||
*
|
||||
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs-types"
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import apiReference from "../docs/developer-docs/api/reference/sidebar";
|
||||
import { collectReleaseFiles, createReleaseSidebarEntries } from "../releases/utils.mjs";
|
||||
import { generateVersionDropdown } from "../src/utils.js";
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
const releases = collectReleaseFiles(path.join(__dirname, "..", "docs"));
|
||||
/**
|
||||
* @type {SidebarItemConfig[]}
|
||||
*/
|
||||
const releases = [
|
||||
"releases/2025/v2025.4",
|
||||
"releases/2025/v2025.2",
|
||||
"releases/2024/v2024.12",
|
||||
{
|
||||
type: "category",
|
||||
label: "Previous versions",
|
||||
items: [
|
||||
"releases/2024/v2024.10",
|
||||
"releases/2024/v2024.8",
|
||||
"releases/2024/v2024.6",
|
||||
"releases/2024/v2024.4",
|
||||
"releases/2024/v2024.2",
|
||||
"releases/2023/v2023.10",
|
||||
"releases/2023/v2023.8",
|
||||
"releases/2023/v2023.6",
|
||||
"releases/2023/v2023.5",
|
||||
"releases/2023/v2023.4",
|
||||
"releases/2023/v2023.3",
|
||||
"releases/2023/v2023.2",
|
||||
"releases/2023/v2023.1",
|
||||
"releases/2022/v2022.12",
|
||||
"releases/2022/v2022.11",
|
||||
"releases/2022/v2022.10",
|
||||
"releases/2022/v2022.9",
|
||||
"releases/2022/v2022.8",
|
||||
"releases/2022/v2022.7",
|
||||
"releases/2022/v2022.6",
|
||||
"releases/2022/v2022.5",
|
||||
"releases/2022/v2022.4",
|
||||
"releases/2022/v2022.2",
|
||||
"releases/2022/v2022.1",
|
||||
"releases/2021/v2021.12",
|
||||
"releases/2021/v2021.10",
|
||||
"releases/2021/v2021.9",
|
||||
"releases/2021/v2021.8",
|
||||
"releases/2021/v2021.7",
|
||||
"releases/2021/v2021.6",
|
||||
"releases/2021/v2021.5",
|
||||
"releases/2021/v2021.4",
|
||||
"releases/2021/v2021.3",
|
||||
"releases/2021/v2021.2",
|
||||
"releases/2021/v2021.1",
|
||||
"releases/old/v0.14",
|
||||
"releases/old/v0.13",
|
||||
"releases/old/v0.12",
|
||||
"releases/old/v0.11",
|
||||
"releases/old/v0.10",
|
||||
"releases/old/v0.9",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @type {SidebarItemConfig[]}
|
||||
*/
|
||||
const items = [
|
||||
{
|
||||
type: "html",
|
||||
value: generateVersionDropdown(releases),
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "index",
|
||||
@ -743,7 +796,7 @@ const items = [
|
||||
slug: "releases",
|
||||
description: "Release Notes for recent authentik versions",
|
||||
},
|
||||
items: createReleaseSidebarEntries(releases),
|
||||
items: releases,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -80,6 +80,7 @@ const items = [
|
||||
"services/argocd/index",
|
||||
"services/awx-tower/index",
|
||||
"services/cloudflare-access/index",
|
||||
"services/coder/index",
|
||||
"services/globalprotect/index",
|
||||
"services/harbor/index",
|
||||
"services/hashicorp-vault/index",
|
||||
|
@ -1,231 +0,0 @@
|
||||
import { usePluginData } from "@docusaurus/useGlobalData";
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser";
|
||||
import type { AKReleasesPluginData } from "@site/releases/plugin.mjs";
|
||||
import clsx from "clsx";
|
||||
import React, { memo, useEffect, useMemo, useState } from "react";
|
||||
import { coerce } from "semver";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
const ProductionURL = new URL("https://docs.goauthentik.io");
|
||||
const LocalhostAliases: ReadonlySet<string> = new Set(["localhost", "127.0.0.1"]);
|
||||
|
||||
/**
|
||||
* Given a semver, create the URL for the version.
|
||||
*/
|
||||
function createVersionURL(semver: string): string {
|
||||
const subdomain = `version-${semver.replace(".", "-")}`;
|
||||
|
||||
return `https://${subdomain}.goauthentik.io`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate to determine if a hostname appears to be a prerelease origin.
|
||||
*/
|
||||
function isPrerelease(hostname: string | null): boolean {
|
||||
if (!hostname) return false;
|
||||
|
||||
if (hostname === ProductionURL.hostname) return true;
|
||||
if (hostname.endsWith(".netlify.app")) return true;
|
||||
|
||||
if (LocalhostAliases.has(hostname)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a hostname, parse the semver from the subdomain.
|
||||
*/
|
||||
function parseHostnameSemVer(hostname: string | null): string | null {
|
||||
if (!hostname) return null;
|
||||
|
||||
const [, possibleSemVer] = hostname.match(/version-(.+)\.goauthentik\.io/) || [];
|
||||
|
||||
if (!possibleSemVer) return null;
|
||||
|
||||
const formattedSemVer = possibleSemVer.replace("-", ".");
|
||||
|
||||
if (!coerce(formattedSemVer)) return null;
|
||||
|
||||
return formattedSemVer;
|
||||
}
|
||||
|
||||
interface VersionDropdownProps {
|
||||
/**
|
||||
* The hostname of the client.
|
||||
*/
|
||||
hostname: string | null;
|
||||
/**
|
||||
* The origin of the prerelease documentation.
|
||||
*
|
||||
* @format url
|
||||
*/
|
||||
prereleaseOrigin: string;
|
||||
/**
|
||||
* The available versions of the documentation.
|
||||
*
|
||||
* @format semver
|
||||
*/
|
||||
releases: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A dropdown that shows the available versions of the documentation.
|
||||
*/
|
||||
const VersionDropdown = memo<VersionDropdownProps>(({ hostname, prereleaseOrigin, releases }) => {
|
||||
const prerelease = isPrerelease(hostname);
|
||||
const parsedSemVer = !prerelease ? parseHostnameSemVer(hostname) : null;
|
||||
|
||||
const currentLabel = parsedSemVer || "Pre-Release";
|
||||
|
||||
const endIndex = parsedSemVer ? releases.indexOf(parsedSemVer) : -1;
|
||||
|
||||
const visibleReleases = releases.slice(0, endIndex === -1 ? 3 : endIndex + 3);
|
||||
|
||||
return (
|
||||
<li className="navbar__item dropdown dropdown--hoverable dropdown--right ak-version-selector">
|
||||
<div
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
role="button"
|
||||
className="navbar__link menu__link"
|
||||
>
|
||||
Version: {currentLabel}
|
||||
</div>
|
||||
|
||||
<ul className="dropdown__menu menu__list-item--collapsed">
|
||||
{!prerelease ? (
|
||||
<li>
|
||||
<a
|
||||
href={prereleaseOrigin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="dropdown__link menu__link"
|
||||
>
|
||||
Pre-Release
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{visibleReleases.map((semVer, idx) => {
|
||||
let label = semVer;
|
||||
|
||||
if (idx === 0) {
|
||||
label += " (Current Release)";
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={idx}>
|
||||
<a
|
||||
href={createVersionURL(semVer)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={clsx("dropdown__link menu__link", {
|
||||
"menu__link--active": semVer === currentLabel,
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
interface VersionPickerLoaderProps {
|
||||
pluginData: AKReleasesPluginData;
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-fetching component that loads available versions of the documentation.
|
||||
*
|
||||
* @see {@linkcode VersionPicker} for the component.
|
||||
* @see {@linkcode AKReleasesPluginData} for the plugin data.
|
||||
* @client
|
||||
*/
|
||||
const VersionPickerLoader: React.FC<VersionPickerLoaderProps> = ({ pluginData }) => {
|
||||
const [releases, setReleases] = useState(pluginData.releases);
|
||||
|
||||
const browser = useIsBrowser();
|
||||
|
||||
const prereleaseOrigin = useMemo(() => {
|
||||
if (browser && LocalhostAliases.has(window.location.hostname)) {
|
||||
return window.location.origin;
|
||||
}
|
||||
|
||||
return ProductionURL.href;
|
||||
}, [browser]);
|
||||
|
||||
const hostname = useMemo(() => {
|
||||
if (!browser) return null;
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Query parameter used for debugging.
|
||||
// Note that this doesn't synchronize with Docusaurus's router state.
|
||||
const subdomain = searchParams.get("version");
|
||||
|
||||
if (subdomain) return subdomain;
|
||||
|
||||
return window.location.hostname;
|
||||
}, [browser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!browser || !prereleaseOrigin) return;
|
||||
|
||||
const controller = new AbortController();
|
||||
const updateURL = new URL(pluginData.publicPath, prereleaseOrigin);
|
||||
|
||||
fetch(updateURL, {
|
||||
signal: controller.signal,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch new releases: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((data: unknown) => {
|
||||
// We're extra cautious here to be ready if the API shape ever changes.
|
||||
if (!data) throw new Error("Failed to parse releases");
|
||||
|
||||
if (!Array.isArray(data)) throw new Error("Releases must be an array");
|
||||
|
||||
if (!data.every((item) => typeof item === "string"))
|
||||
throw new Error("Releases must be an array of strings");
|
||||
|
||||
setReleases(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn(`Failed to fetch new releases: ${error}`);
|
||||
});
|
||||
|
||||
return () => controller.abort("unmount");
|
||||
}, [browser, prereleaseOrigin]);
|
||||
|
||||
return (
|
||||
<VersionDropdown
|
||||
hostname={hostname}
|
||||
prereleaseOrigin={prereleaseOrigin}
|
||||
releases={releases}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that shows the available versions of the documentation.
|
||||
*
|
||||
* @see {@linkcode VersionPickerLoader} for the data-fetching component.
|
||||
*/
|
||||
export const VersionPicker: React.FC = () => {
|
||||
const pluginData = usePluginData("ak-releases-plugin", undefined, {
|
||||
failfast: true,
|
||||
}) as AKReleasesPluginData;
|
||||
|
||||
if (!pluginData.releases.length) return null;
|
||||
|
||||
return <VersionPickerLoader pluginData={pluginData} />;
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
.theme-doc-sidebar-menu .dropdown.ak-version-selector {
|
||||
--ak-version-selector-padding: calc(var(--ifm-spacing-vertical) / 2);
|
||||
|
||||
width: calc(100% - (var(--ifm-spacing-horizontal) / 2));
|
||||
border-block-end: var(--ifm-hr-height) solid var(--ifm-toc-border-color);
|
||||
padding-block-start: calc(var(--ak-version-selector-padding) / 2);
|
||||
padding-block-end: var(--ak-version-selector-padding);
|
||||
margin-block-end: var(--ak-version-selector-padding);
|
||||
|
||||
.navbar__link.menu__link {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
font-weight: var(--ifm-font-weight-semibold);
|
||||
|
||||
&::after {
|
||||
color: var(--ifm-color-emphasis-400);
|
||||
filter: var(--ifm-menu-link-sublist-icon-filter);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown__menu {
|
||||
background: var(--ifm-dropdown-background-color);
|
||||
box-shadow: var(--ifm-global-shadow-lw);
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import {
|
||||
DocSidebarItemsExpandedStateProvider,
|
||||
useVisibleSidebarItems,
|
||||
} from "@docusaurus/plugin-content-docs/client";
|
||||
import { VersionPicker } from "@site/src/components/VersionPicker/index";
|
||||
import DocSidebarItem from "@theme/DocSidebarItem";
|
||||
import type { Props as DocSidebarItemsProps } from "@theme/DocSidebarItems";
|
||||
import { memo } from "react";
|
||||
|
||||
const DocSidebarItems: React.FC<DocSidebarItemsProps> = ({ items, ...props }) => {
|
||||
const visibleItems = useVisibleSidebarItems(items, props.activePath);
|
||||
const includeVersionPicker = props.level === 1 && props.activePath.startsWith("/docs");
|
||||
|
||||
return (
|
||||
<DocSidebarItemsExpandedStateProvider>
|
||||
{includeVersionPicker ? <VersionPicker /> : null}
|
||||
{visibleItems.map((item, index) => (
|
||||
<DocSidebarItem key={index} item={item} index={index} {...props} />
|
||||
))}
|
||||
</DocSidebarItemsExpandedStateProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DocSidebarItems);
|
3
website/types/docusaurus.d.ts
vendored
3
website/types/docusaurus.d.ts
vendored
@ -21,9 +21,6 @@ declare module "@docusaurus/plugin-content-docs/src/sidebars/types" {
|
||||
}
|
||||
|
||||
declare module "@docusaurus/plugin-content-docs/client" {
|
||||
export * from "@docusaurus/plugin-content-docs/lib/client/docSidebarItemsExpandedState.js";
|
||||
export * from "@docusaurus/plugin-content-docs/lib/client/docsUtils.js";
|
||||
|
||||
import { DocContextValue as BaseDocContextValue } from "@docusaurus/plugin-content-docs/lib/client/doc.js";
|
||||
import { DocFrontMatter as BaseDocFrontMatter } from "@docusaurus/plugin-content-docs";
|
||||
|
||||
|
Reference in New Issue
Block a user