Compare commits

...

23 Commits

Author SHA1 Message Date
af9ce90b8b fixup! core: fix group creation for limited permission users 2025-05-14 17:47:08 +02:00
05e5c6309c core: fix group creation for limited permission users
Broken by https://github.com/goauthentik/authentik/pull/12900
2025-05-14 17:27:59 +02:00
ad4a765a80 website: bump the build group in /website with 6 updates (#14502)
Bumps the build group in /website with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@rspack/binding-darwin-arm64](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.3.9` | `1.3.10` |
| [@rspack/binding-linux-arm64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.3.9` | `1.3.10` |
| [@rspack/binding-linux-x64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.3.9` | `1.3.10` |
| [lightningcss-darwin-arm64](https://github.com/parcel-bundler/lightningcss) | `1.30.0` | `1.30.1` |
| [lightningcss-linux-arm64-gnu](https://github.com/parcel-bundler/lightningcss) | `1.30.0` | `1.30.1` |
| [lightningcss-linux-x64-gnu](https://github.com/parcel-bundler/lightningcss) | `1.30.0` | `1.30.1` |


Updates `@rspack/binding-darwin-arm64` from 1.3.9 to 1.3.10
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.3.10/packages/rspack)

Updates `@rspack/binding-linux-arm64-gnu` from 1.3.9 to 1.3.10
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.3.10/packages/rspack)

Updates `@rspack/binding-linux-x64-gnu` from 1.3.9 to 1.3.10
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.3.10/packages/rspack)

Updates `lightningcss-darwin-arm64` from 1.30.0 to 1.30.1
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.30.0...v1.30.1)

Updates `lightningcss-linux-arm64-gnu` from 1.30.0 to 1.30.1
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.30.0...v1.30.1)

Updates `lightningcss-linux-x64-gnu` from 1.30.0 to 1.30.1
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.30.0...v1.30.1)

---
updated-dependencies:
- dependency-name: "@rspack/binding-darwin-arm64"
  dependency-version: 1.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-arm64-gnu"
  dependency-version: 1.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-x64-gnu"
  dependency-version: 1.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: lightningcss-darwin-arm64
  dependency-version: 1.30.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: lightningcss-linux-arm64-gnu
  dependency-version: 1.30.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: lightningcss-linux-x64-gnu
  dependency-version: 1.30.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 16:11:52 +02:00
4dcd481010 core: remove OldAuthenticatedSession content type (#14507)
* core: remove `OldAuthenticatedSession` content type

This was left out from https://github.com/goauthentik/authentik/pull/9736

* remove stale content types in `repair_permissions`

Co-authored-by: Jens Langhammer <jens@goauthentik.io>

* run `remove_stale_contenttypes` for each tenant

---------

Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-05-14 15:02:29 +02:00
d0dc14d84d core: bump msgraph-sdk from 1.29.0 to 1.30.0 (#14503)
Bumps [msgraph-sdk](https://github.com/microsoftgraph/msgraph-sdk-python) from 1.29.0 to 1.30.0.
- [Release notes](https://github.com/microsoftgraph/msgraph-sdk-python/releases)
- [Changelog](https://github.com/microsoftgraph/msgraph-sdk-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/microsoftgraph/msgraph-sdk-python/compare/v1.29.0...v1.30.0)

---
updated-dependencies:
- dependency-name: msgraph-sdk
  dependency-version: 1.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 12:22:41 +02:00
7bf960352b core: bump twilio from 9.6.0 to 9.6.1 (#14505)
Bumps [twilio](https://github.com/twilio/twilio-python) from 9.6.0 to 9.6.1.
- [Release notes](https://github.com/twilio/twilio-python/releases)
- [Changelog](https://github.com/twilio/twilio-python/blob/main/CHANGES.md)
- [Commits](https://github.com/twilio/twilio-python/compare/9.6.0...9.6.1)

---
updated-dependencies:
- dependency-name: twilio
  dependency-version: 9.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 12:22:27 +02:00
c07d01661b core: bump psycopg[c,pool] from 3.2.8 to 3.2.9 (#14504)
Bumps [psycopg[c,pool]](https://github.com/psycopg/psycopg) from 3.2.8 to 3.2.9.
- [Changelog](https://github.com/psycopg/psycopg/blob/3.2.9/docs/news.rst)
- [Commits](https://github.com/psycopg/psycopg/compare/3.2.8...3.2.9)

---
updated-dependencies:
- dependency-name: psycopg[c,pool]
  dependency-version: 3.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 12:19:42 +02:00
427597ec14 enterprise: fix expired license's users being counted (#14451)
* enterprise: fix expired license's users being counted

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* tests to the rescue

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* hmm

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-13 15:59:17 +02:00
7cc77bd387 website/integrations: fix missing closing brace for semaphore (#14467)
Update index.mdx

Added missing closing bracket

Signed-off-by: ericgu08 <79233593+ericgu08@users.noreply.github.com>
2025-05-13 15:26:10 +02:00
381a1a2c49 tests/e2e: Add E2E tests for Flow SFE (#14484)
* add e2e test for SFE login

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add helper text in SFE on password stage

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* build sfe for e2e

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix ci e2e cache key not considering sfe

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix sfe missing from docker build

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* sigh I forgot npm

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-13 12:03:12 +02:00
08f8222224 website: bump semver from 7.7.1 to 7.7.2 in /website (#14491)
Bumps [semver](https://github.com/npm/node-semver) from 7.7.1 to 7.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.7.1...v7.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-version: 7.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 12:01:13 +02:00
1211c34a18 core: bump django from 5.1.8 to 5.1.9 (#14483)
* build(deps): bump django from 5.1.8 to 5.1.9

Bumps [django](https://github.com/django/django) from 5.1.8 to 5.1.9.
- [Commits](https://github.com/django/django/compare/5.1.8...5.1.9)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 5.1.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* bump lock

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-05-12 21:23:10 +02:00
22efb57369 core: bump psycopg[c,pool] from 3.2.7 to 3.2.8 (#14481)
Bumps [psycopg[c,pool]](https://github.com/psycopg/psycopg) from 3.2.7 to 3.2.8.
- [Changelog](https://github.com/psycopg/psycopg/blob/master/docs/news.rst)
- [Commits](https://github.com/psycopg/psycopg/compare/3.2.7...3.2.8)

---
updated-dependencies:
- dependency-name: psycopg[c,pool]
  dependency-version: 3.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 20:55:37 +02:00
3eeda53be6 core: bump sentry-sdk from 2.27.0 to 2.28.0 (#14482)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.27.0 to 2.28.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.27.0...2.28.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 20:55:34 +02:00
82ace18703 root: pin package version in pyproject for dependabot (#14469)
* root: pin package version in pyproject for dependabot

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use exact as we know that works now

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-12 19:32:48 +02:00
8589079252 core: fix session migration when old session can't be loaded (#14466)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-12 15:51:49 +02:00
ae2af6e58e root: temporarily deactivate database pool option (#14443)
* root: temporarily deactivate database pool option

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* deactivate tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-12 14:48:01 +02:00
86a7f98ff6 website: bump the build group in /website with 3 updates (#14475)
Bumps the build group in /website with 3 updates: [lightningcss-darwin-arm64](https://github.com/parcel-bundler/lightningcss), [lightningcss-linux-arm64-gnu](https://github.com/parcel-bundler/lightningcss) and [lightningcss-linux-x64-gnu](https://github.com/parcel-bundler/lightningcss).


Updates `lightningcss-darwin-arm64` from 1.29.3 to 1.30.0
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.29.3...v1.30.0)

Updates `lightningcss-linux-arm64-gnu` from 1.29.3 to 1.30.0
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.29.3...v1.30.0)

Updates `lightningcss-linux-x64-gnu` from 1.29.3 to 1.30.0
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.29.3...v1.30.0)

---
updated-dependencies:
- dependency-name: lightningcss-darwin-arm64
  dependency-version: 1.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: lightningcss-linux-arm64-gnu
  dependency-version: 1.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: lightningcss-linux-x64-gnu
  dependency-version: 1.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 14:39:18 +02:00
3af45371d3 website/docs: stages: fix-typo (#14477)
Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2025-05-12 14:12:33 +02:00
b01ffd934f website/docs: Update Kubernetes Bootstrap Instructions (#14471)
* website/docs: update envFrom block for automated install

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-05-11 15:13:14 +02:00
f11ba94603 root: improve sentry distributed tracing (#14468)
* core: include all sentry headers

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove spotlight patch we dont need anymore

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* always trace in debug

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* init sentry earlier

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-add light interface

https://github.com/goauthentik/authentik/pull/14331

removes 2 unneeded API calls

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* sentry integrated router

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use new Sentry middleware to propagate headers

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing baggage

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* cleanup logs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use sanitized URLs for logging/tracing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-11 02:40:31 +02:00
7d2aa43364 Revert "web/admin: fix enterprise menu display" (#14458)
Revert "web/admin: fix enterprise menu display (#14447)"

This reverts commit 0611eea0e7.
2025-05-10 18:26:07 +02:00
f1351a7577 website/docs: update outdated custom CSS docs (#14441)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-10 11:15:47 -05:00
45 changed files with 751 additions and 606 deletions

View File

@ -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 }}

View File

@ -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

View File

@ -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(),
}

View File

@ -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):

View File

@ -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)

View File

@ -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(

View File

@ -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,
),
]

View File

@ -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 %}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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": {

View File

@ -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

View File

@ -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,
# }
# },
# )

View File

@ -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]

View 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)

View File

@ -241,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)

251
uv.lock generated
View File

@ -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]]
@ -571,30 +571,30 @@ wheels = [
[[package]]
name = "boto3"
version = "1.38.10"
version = "1.38.13"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5f/26/c4a2f1c64efb5ae6b47b94cb543282ab5770aa2c4562aba6934af628cf76/boto3-1.38.10.tar.gz", hash = "sha256:af4c78a3faa1a56cbaeb9e06cd5580772138be519fc6e740b81db586d5d1910c", size = 111837, upload-time = "2025-05-06T19:29:55.088Z" }
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/2f/fe/2b69dcdd433c32ba80b36eabfe799e8c3e0b08ff3e0fc06bc2e1cc065a19/boto3-1.38.10-py3-none-any.whl", hash = "sha256:26113a47d549bc3c46dbf56c8ab74f272c3da55df23e2c460fcf3c6c64d54dce", size = 139911, upload-time = "2025-05-06T19:29:51.823Z" },
{ 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.10"
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/c0/18/c03b763c831e269d76a7c0fcba53802f99bf68f8d4530af672ae96a6d343/botocore-1.38.10.tar.gz", hash = "sha256:c531c13803e0fad5b395c5ccab4c11ac88acfccde71c9b998df6fa841392a8fc", size = 13881598, upload-time = "2025-05-06T19:29:41.315Z" }
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/f9/92/2c522e277c95d35b4b83bff6a3839875d91b0d835a93545828a7046013c4/botocore-1.38.10-py3-none-any.whl", hash = "sha256:5244454bb9e8fbb6510145d1554e82fd243e8583507d83077ecf4f8efb66cb46", size = 13539530, upload-time = "2025-05-06T19:29:35.384Z" },
{ 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]]
@ -2065,7 +2065,7 @@ wheels = [
[[package]]
name = "msgraph-sdk"
version = "1.29.0"
version = "1.30.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-identity" },
@ -2075,9 +2075,9 @@ dependencies = [
{ name = "microsoft-kiota-serialization-text" },
{ name = "msgraph-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/31/f37dd23324f6b23008fa19f6cd8d44edec803a4b750239ac005d085fe46a/msgraph_sdk-1.29.0.tar.gz", hash = "sha256:15cfb13d81df65395a663159a9e234a097cca731d60ebc8a3f4016443ba72602", size = 6593690, upload-time = "2025-05-08T12:52:20.528Z" }
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/68/4f/735ff127e05d317ea0af233c0d6ac9e8644a0501e1523046a3fc799c5af7/msgraph_sdk-1.29.0-py3-none-any.whl", hash = "sha256:a52b13e95221da074fd001ccac19b54ebba40400dc4250af58417ac21f7fbed8", size = 27069538, upload-time = "2025-05-08T12:52:17.151Z" },
{ 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]]
@ -2396,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]
@ -2416,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"
@ -2940,15 +2940,15 @@ wheels = [
[[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]]
@ -3000,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]]
@ -3099,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]]
@ -3160,7 +3160,7 @@ wheels = [
[[package]]
name = "twilio"
version = "9.6.0"
version = "9.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@ -3168,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]]
@ -3487,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
View File

@ -7,7 +7,6 @@
"": {
"name": "@goauthentik/web",
"version": "0.0.0",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
".",

View File

@ -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",

View File

@ -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);
}

View File

@ -210,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")}

View File

@ -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

View File

@ -10,7 +10,6 @@ import { configureSentry } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users";
import { WebsocketClient } from "@goauthentik/common/ws";
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
import { WithCapabilitiesConfig } from "@goauthentik/elements/Interface/capabilitiesProvider";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
import "@goauthentik/elements/ak-locale-context";
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
@ -36,7 +35,7 @@ import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { CapabilitiesEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
import { LicenseSummaryStatusEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
import {
AdminSidebarEnterpriseEntries,
@ -49,9 +48,7 @@ if (process.env.NODE_ENV === "development") {
}
@customElement("ak-interface-admin")
export class AdminInterface extends WithCapabilitiesConfig(
WithLicenseSummary(AuthenticatedInterface),
) {
export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
//#region Properties
@property({ type: Boolean })
@ -134,9 +131,9 @@ export class AdminInterface extends WithCapabilitiesConfig(
//#region Lifecycle
constructor() {
configureSentry(true);
super();
this.ws = new WebsocketClient();
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
this.sidebarOpen = this.#sidebarMatcher.matches;
}
@ -170,7 +167,6 @@ export class AdminInterface extends WithCapabilitiesConfig(
}
async firstUpdated(): Promise<void> {
configureSentry(true);
this.user = await me();
const canAccessAdmin =
@ -207,7 +203,7 @@ export class AdminInterface extends WithCapabilitiesConfig(
<ak-sidebar class="${classMap(sidebarClasses)}">
${renderSidebarItems(AdminSidebarEntries)}
${this.can(CapabilitiesEnum.IsEnterprise)
${this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed
? renderSidebarItems(AdminSidebarEnterpriseEntries)
: nothing}
</ak-sidebar>

View File

@ -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(),
],
});

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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,
)}>`;
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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"

View File

@ -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);

View File

@ -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).

View File

@ -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:

View File

@ -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>

View File

@ -1,5 +1,3 @@
## Global customization
### Global customization
import CustomCSS from "./customcss.mdx";
<CustomCSS />
See [Brand Settings](../../../sys-mgmt/brands.md#branding-settings)

View File

@ -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.

View File

@ -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

View File

@ -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).

View File

@ -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

View File

@ -61,6 +61,7 @@ Add the `oidc_providers` configuration:
"name_claim": "name",
"email_claim": "email",
"scopes": ["openid", "profile", "email"]
}
},
...
}

View File

@ -19,10 +19,10 @@
"@goauthentik/docusaurus-config": "^1.0.6",
"@goauthentik/tsconfig": "^1.0.4",
"@mdx-js/react": "^3.1.0",
"@rspack/binding-linux-x64-gnu": "1.3.9",
"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",
@ -30,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",
@ -51,18 +51,18 @@
"node": ">=22.14.0"
},
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "1.3.9",
"@rspack/binding-linux-arm64-gnu": "1.3.9",
"@rspack/binding-linux-x64-gnu": "1.3.9",
"@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": {
@ -4676,9 +4676,9 @@
}
},
"node_modules/@rspack/binding-darwin-arm64": {
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.9.tgz",
"integrity": "sha512-lfTmsbUGab9Ak/X6aPLacHLe4MBRra+sLmhoNK8OKEN3qQCjDcomwW5OlmBRV5bcUYWdbK8vgDk2HUUXRuibVg==",
"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"
],
@ -4703,9 +4703,9 @@
"peer": true
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.9.tgz",
"integrity": "sha512-pBKnS2Fbn9cDtWe1KcD1qRjQlJwQhP9pFW2KpxdjE7qXbaO11IHtem6dLZwdpNqbDn9QgyfdVGXBDvBaP1tGwA==",
"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"
],
@ -4730,9 +4730,9 @@
"peer": true
},
"node_modules/@rspack/binding-linux-x64-gnu": {
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.9.tgz",
"integrity": "sha512-82izGJw/qxJ4xaHJy/A4MF7aTRT9tE6VlWoWM4rJmqRszfujN/w54xJRie9jkt041TPvJWGNpYD4Hjpt0/n/oA==",
"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"
],
@ -14469,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"
],
@ -14549,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"
],
@ -14589,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"
],
@ -22638,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"

View File

@ -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.9",
"@rspack/binding-linux-arm64-gnu": "1.3.9",
"@rspack/binding-linux-x64-gnu": "1.3.9",
"@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"