Compare commits

..

22 Commits

Author SHA1 Message Date
c4271b1ff7 argh 2025-04-30 17:53:59 -05:00
d37f025881 tweak 2025-04-30 17:43:06 -05:00
149815885d final tweaks 2025-04-30 17:23:48 -05:00
be46545613 tweak to remove gerunds 2025-04-30 17:17:34 -05:00
9d3822373a rework intro 2025-04-30 17:16:48 -05:00
ab84adc13f Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
2025-04-30 17:16:48 -05:00
689c59fe1c Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
b8578623fc Update device_code.md
removed newline as per "prettier --write"

Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
ae4947e85b Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
5e840baf84 Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
df5ec2c020 Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
f4c64b49c2 Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
1c022cb366 Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
ebd620562c Update website/docs/add-secure-apps/providers/oauth2/device_code.md
Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
7ecdc1e681 Fixed relative path
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
56bb76c4ef Fixed formatting according to style guide
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
8c2ba9176b "Relative vs. absolute paths" rule
Removed full link in favor of relative path

Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
11cbde771b Added step-by-step tutorial for setup
Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
14b6fc5d85 SEO Optimization
Added alternative name to "also known" section to improve searchability

Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
5d8630595d Improved UX
Marked keywords with Capital letters and proper formatting to clarify those are references to actual values/labels

Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
54f4b83cf7 Improved documentation
Added link for brand keyword, removed repetition

Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
3b6de494c9 Improved RFC reference
Replaced "abilities" with "capabilities" to better reflect RFC wording, added extended summary from RFC to ensure complete and clear understanding.

Signed-off-by: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com>
2025-04-30 17:16:48 -05:00
371 changed files with 9276 additions and 15314 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2025.4.1
current_version = 2025.4.0
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -118,15 +118,3 @@ updates:
prefix: "core:"
labels:
- dependencies
- package-ecosystem: docker-compose
directories:
# - /scripts # Maybe
- /tests/e2e
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: "core:"
labels:
- dependencies

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/**', 'web/packages/sfe/src/**') }}-b
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
@ -208,7 +208,6 @@ 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

@ -29,7 +29,7 @@ jobs:
- name: Generate API
run: make gen-client-go
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v7
with:
version: latest
args: --timeout 5000s --verbose

View File

@ -16,7 +16,7 @@
],
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.tsdk": "./web/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"yaml.schemas": {
"./blueprints/schema.json": "blueprints/**/*.yaml"
@ -30,5 +30,7 @@
}
],
"go.testFlags": ["-count=1"],
"github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"]
"github-actions.workflows.pinned.workflows": [
".github/workflows/ci-main.yml"
]
}

View File

@ -40,8 +40,7 @@ COPY ./web /work/web/
COPY ./website /work/website/
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build && \
npm run build:sfe
RUN npm run build
# Stage 3: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
@ -86,17 +85,18 @@ FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="1"
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"
USER root
RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
--mount=type=secret,id=GEOIPUPDATE_LICENSE_KEY \
mkdir -p /usr/share/GeoIP && \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 5: Download uv
FROM ghcr.io/astral-sh/uv:0.7.4 AS uv
FROM ghcr.io/astral-sh/uv:0.7.0 AS uv
# Stage 6: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \

View File

@ -42,4 +42,4 @@ See [SECURITY.md](SECURITY.md)
## Adoption and Contributions
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [contribution guide](https://docs.goauthentik.io/docs/developer-docs?utm_source=github).
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [CONTRIBUTING.md file](./CONTRIBUTING.md).

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2025.4.1"
__version__ = "2025.4.0"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -54,7 +54,7 @@ def create_component(generator: SchemaGenerator, name, schema, type_=ResolvedCom
return component
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs):
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs): # noqa: W0613
"""Workaround to set a default response for endpoints.
Workaround suggested at
<https://github.com/tfranzel/drf-spectacular/issues/119#issuecomment-656970357>

View File

@ -164,7 +164,9 @@ class BlueprintEntry:
"""Get the blueprint model, with yaml tags resolved if present"""
return str(self.tag_resolver(self.model, blueprint))
def get_permissions(self, blueprint: "Blueprint") -> Generator[BlueprintEntryPermission]:
def get_permissions(
self, blueprint: "Blueprint"
) -> Generator[BlueprintEntryPermission, None, None]:
"""Get permissions of this entry, with all yaml tags resolved"""
for perm in self.permissions:
yield BlueprintEntryPermission(

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,9 +32,13 @@ 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,
"html_meta": {**get_http_meta()},
"sentry_trace": trace,
"version": get_full_version(),
}

View File

@ -99,17 +99,18 @@ class GroupSerializer(ModelSerializer):
if superuser
else "authentik_core.disable_group_superuser"
)
if self.instance or superuser:
has_perm = user.has_perm(perm) or 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})
)
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:

View File

@ -2,7 +2,6 @@
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
@ -17,10 +16,6 @@ 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

@ -1,9 +1,123 @@
# Generated by Django 5.0.11 on 2025-01-27 12:58
import uuid
import pickle # nosec
from django.core import signing
from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, SESSION_KEY
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.utils.timezone import now, timedelta
from authentik.lib.migrations import progress_bar
from authentik.root.middleware import ClientIPMiddleware
SESSION_CACHE_ALIAS = "default"
class PickleSerializer:
"""
Simple wrapper around pickle to be used in signing.dumps()/loads() and
cache backends.
"""
def __init__(self, protocol=None):
self.protocol = pickle.HIGHEST_PROTOCOL if protocol is None else protocol
def dumps(self, obj):
"""Pickle data to be stored in redis"""
return pickle.dumps(obj, self.protocol)
def loads(self, data):
"""Unpickle data to be loaded from redis"""
return pickle.loads(data) # nosec
def _migrate_session(
apps,
db_alias,
session_key,
session_data,
expires,
):
Session = apps.get_model("authentik_core", "Session")
OldAuthenticatedSession = apps.get_model("authentik_core", "OldAuthenticatedSession")
AuthenticatedSession = apps.get_model("authentik_core", "AuthenticatedSession")
old_auth_session = (
OldAuthenticatedSession.objects.using(db_alias).filter(session_key=session_key).first()
)
args = {
"session_key": session_key,
"expires": expires,
"last_ip": ClientIPMiddleware.default_ip,
"last_user_agent": "",
"session_data": {},
}
for k, v in session_data.items():
if k == "authentik/stages/user_login/last_ip":
args["last_ip"] = v
elif k in ["last_user_agent", "last_used"]:
args[k] = v
elif args in [SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY]:
pass
else:
args["session_data"][k] = v
if old_auth_session:
args["last_user_agent"] = old_auth_session.last_user_agent
args["last_used"] = old_auth_session.last_used
args["session_data"] = pickle.dumps(args["session_data"])
session = Session.objects.using(db_alias).create(**args)
if old_auth_session:
AuthenticatedSession.objects.using(db_alias).create(
session=session,
user=old_auth_session.user,
)
def migrate_redis_sessions(apps, schema_editor):
from django.core.cache import caches
db_alias = schema_editor.connection.alias
cache = caches[SESSION_CACHE_ALIAS]
# Not a redis cache, skipping
if not hasattr(cache, "keys"):
return
print("\nMigrating Redis sessions to database, this might take a couple of minutes...")
for key, session_data in progress_bar(cache.get_many(cache.keys(f"{KEY_PREFIX}*")).items()):
_migrate_session(
apps=apps,
db_alias=db_alias,
session_key=key.removeprefix(KEY_PREFIX),
session_data=session_data,
expires=now() + timedelta(seconds=cache.ttl(key)),
)
def migrate_database_sessions(apps, schema_editor):
DjangoSession = apps.get_model("sessions", "Session")
db_alias = schema_editor.connection.alias
print("\nMigration database sessions, this might take a couple of minutes...")
for django_session in progress_bar(DjangoSession.objects.using(db_alias).all()):
session_data = signing.loads(
django_session.session_data,
salt="django.contrib.sessions.SessionStore",
serializer=PickleSerializer,
)
_migrate_session(
apps=apps,
db_alias=db_alias,
session_key=django_session.session_key,
session_data=session_data,
expires=django_session.expire_date,
)
class Migration(migrations.Migration):
@ -113,4 +227,12 @@ class Migration(migrations.Migration):
"verbose_name_plural": "Authenticated Sessions",
},
),
migrations.RunPython(
code=migrate_redis_sessions,
reverse_code=migrations.RunPython.noop,
),
migrations.RunPython(
code=migrate_database_sessions,
reverse_code=migrations.RunPython.noop,
),
]

View File

@ -1,27 +0,0 @@
# 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,9 +21,7 @@
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
{% block head %}
{% endblock %}
{% for key, value in html_meta.items %}
<meta name="{{key}}" content="{{ value }}" />
{% endfor %}
<meta name="sentry-trace" content="{{ sentry_trace }}" />
</head>
<body>
{% block body %}

View File

@ -124,16 +124,6 @@ class TestGroupsAPI(APITestCase):
{"is_superuser": ["User does not have permission to set superuser status to True."]},
)
def test_superuser_no_perm_no_superuser(self):
"""Test creating a group without permission and without superuser flag"""
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": False},
)
self.assertEqual(res.status_code, 201)
def test_superuser_update_no_perm(self):
"""Test updating a superuser group without permission"""
group = Group.objects.create(name=generate_id(), is_superuser=True)

View File

@ -132,14 +132,13 @@ 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():
if lic.is_valid:
total.internal_users += lic.internal_users
total.external_users += lic.external_users
total.license_flags.extend(lic.status.license_flags)
total.internal_users += lic.internal_users
total.external_users += lic.external_users
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,10 +39,6 @@ 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,7 +8,6 @@ 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,
@ -72,9 +71,9 @@ class TestEnterpriseLicense(TestCase):
)
def test_valid_multiple(self):
"""Check license verification"""
lic = License.objects.create(key=generate_id(), expiry=expiry_valid)
lic = License.objects.create(key=generate_id())
self.assertTrue(lic.status.status().is_valid)
lic2 = License.objects.create(key=generate_id(), expiry=expiry_valid)
lic2 = License.objects.create(key=generate_id())
self.assertTrue(lic2.status.status().is_valid)
total = LicenseKey.get_total()
self.assertEqual(total.internal_users, 200)
@ -233,9 +232,7 @@ class TestEnterpriseLicense(TestCase):
)
def test_expiry_expired(self):
"""Check license verification"""
User.objects.all().delete()
License.objects.all().delete()
License.objects.create(key=generate_id(), expiry=expiry_expired)
License.objects.create(key=generate_id())
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRED)
@patch(

View File

@ -57,7 +57,7 @@ class LogEventSerializer(PassiveSerializer):
@contextmanager
def capture_logs(log_default_output=True) -> Generator[list[LogEvent]]:
def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]:
"""Capture log entries created"""
logs = []
cap = LogCapture()

View File

@ -15,7 +15,6 @@
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
<meta name="sentry-trace" content="{{ sentry_trace }}" />
<link rel="prefetch" href="{{ flow_background_url }}" />
{% include "base/header_js.html" %}
<style>
html,
@ -23,7 +22,7 @@
height: 100%;
}
body {
background-image: url("{{ flow_background_url }}");
background-image: url("{{ flow.background_url }}");
background-repeat: no-repeat;
background-size: cover;
}

View File

@ -5,9 +5,9 @@
{% block head_before %}
{{ block.super }}
<link rel="prefetch" href="{{ flow_background_url }}" />
<link rel="prefetch" href="{{ flow.background_url }}" />
{% if flow.compatibility_mode and not inspector %}
<script>ShadyDOM = { force: true };</script>
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
{% include "base/header_js.html" %}
<script>
@ -21,7 +21,7 @@ window.authentik.flow = {
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
<style>
:root {
--ak-flow-background: url("{{ flow_background_url }}");
--ak-flow-background: url("{{ flow.background_url }}");
}
</style>
{% endblock %}

View File

@ -13,9 +13,7 @@ class FlowInterfaceView(InterfaceView):
"""Flow interface"""
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
kwargs["flow"] = flow
kwargs["flow_background_url"] = flow.background_url(self.request)
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
kwargs["inspector"] = "inspector" in self.request.GET
return super().get_context_data(**kwargs)

View File

@ -363,9 +363,6 @@ 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, get_current_scope
from sentry_sdk import HttpTransport
from sentry_sdk import init as sentry_sdk_init
from sentry_sdk.api import set_tag
from sentry_sdk.integrations.argv import ArgvIntegration
@ -27,7 +27,6 @@ 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
@ -96,8 +95,6 @@ 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))
@ -170,14 +167,3 @@ 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

@ -59,7 +59,7 @@ class PropertyMappingManager:
request: HttpRequest | None,
return_mapping: bool = False,
**kwargs,
) -> Generator[tuple[dict, PropertyMapping]]:
) -> Generator[tuple[dict, PropertyMapping], None]:
"""Iterate over all mappings that were pre-compiled and
execute all of them with the given context"""
if not self.__has_compiled:

View File

@ -494,88 +494,86 @@ class TestConfig(TestCase):
},
)
# 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(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

@ -199,7 +199,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
chunk_size = len(ops)
if len(ops) < 1:
return
for chunk in batched(ops, chunk_size, strict=False):
for chunk in batched(ops, chunk_size):
req = PatchRequest(Operations=list(chunk))
self._request(
"PATCH",

View File

@ -317,7 +317,7 @@ class KerberosSource(Source):
usage="accept", name=name, store=self.get_gssapi_store()
)
except gssapi.exceptions.GSSError as exc:
LOGGER.warning("GSSAPI credentials failure", exc=exc)
LOGGER.warn("GSSAPI credentials failure", exc=exc)
return None

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object",
"title": "authentik 2025.4.1 Blueprint schema",
"title": "authentik 2025.4.0 Blueprint schema",
"required": [
"version",
"entries"

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.0}
restart: unless-stopped
command: server
environment:
@ -55,7 +55,7 @@ services:
redis:
condition: service_healthy
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.0}
restart: unless-stopped
command: worker
environment:

16
go.mod
View File

@ -5,7 +5,7 @@ go 1.24.0
require (
beryju.io/ldap v0.1.0
github.com/coreos/go-oidc/v3 v3.14.1
github.com/getsentry/sentry-go v0.33.0
github.com/getsentry/sentry-go v0.32.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-openapi/runtime v0.28.0
@ -19,18 +19,18 @@ require (
github.com/jellydator/ttlcache/v3 v3.3.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.8.1
github.com/pires/go-proxyproto v0.8.0
github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.8.0
github.com/sethvargo/go-envconfig v1.3.0
github.com/redis/go-redis/v9 v9.7.3
github.com/sethvargo/go-envconfig v1.2.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025041.1
goauthentik.io/api/v3 v3.2025024.9
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.14.0
golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.13.0
gopkg.in/yaml.v2 v2.4.0
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
)
@ -75,7 +75,7 @@ require (
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

36
go.sum
View File

@ -69,8 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@ -230,8 +230,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -245,14 +245,14 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sethvargo/go-envconfig v1.3.0 h1:gJs+Fuv8+f05omTpwWIu6KmuseFAXKrIaOZSh8RMt0U=
github.com/sethvargo/go-envconfig v1.3.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
github.com/sethvargo/go-envconfig v1.2.0 h1:q3XkOZWkC+G1sMLCrw9oPGTjYexygLOXDmGUit1ti8Q=
github.com/sethvargo/go-envconfig v1.2.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@ -290,8 +290,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025041.1 h1:GAN6AoTmfnCGgx1SyM07jP4/LR/T3rkTEyShSBd3Co8=
goauthentik.io/api/v3 v3.2025041.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025024.9 h1:i3tbkyotE32ZpJ729BsPWTuLQUdtZ54Li4aP1amZzsM=
goauthentik.io/api/v3 v3.2025024.9/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -358,16 +358,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -376,8 +376,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -412,8 +412,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2025.4.1"
const VERSION = "2025.4.0"

View File

@ -56,7 +56,6 @@ EXPOSE 3389 6636 9300
USER 1000
ENV TMPDIR=/dev/shm/ \
GOFIPS=1
ENV GOFIPS=1
ENTRYPOINT ["/ldap"]

View File

@ -97,7 +97,6 @@ elif [[ "$1" == "test-all" ]]; then
elif [[ "$1" == "healthcheck" ]]; then
run_authentik healthcheck $(cat $MODE_FILE)
elif [[ "$1" == "dump_config" ]]; then
shift
exec python -m authentik.lib.config $@
elif [[ "$1" == "debug" ]]; then
exec sleep infinity

View File

@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1015.0",
"aws-cdk": "^2.1012.0",
"cross-env": "^7.0.3"
},
"engines": {
@ -17,9 +17,9 @@
}
},
"node_modules/aws-cdk": {
"version": "2.1015.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1015.0.tgz",
"integrity": "sha512-txd+yMVVybtLfiwT409+fahbP0SkiwhmQvQf6PVVYnWzDPSknxYlUNJHisHV4tJEcbHWn1QPsLmqqMT0bw8hBg==",
"version": "2.1012.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1012.0.tgz",
"integrity": "sha512-C6jSWkqP0hkY2Cs300VJHjspmTXDTMfB813kwZvRbd/OsKBfTBJBbYU16VoLAp1LVEOnQMf8otSlaSgzVF0X9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@ -10,7 +10,7 @@
"node": ">=20"
},
"devDependencies": {
"aws-cdk": "^2.1015.0",
"aws-cdk": "^2.1012.0",
"cross-env": "^7.0.3"
}
}

View File

@ -26,7 +26,7 @@ Parameters:
Description: authentik Docker image
AuthentikVersion:
Type: String
Default: 2025.4.1
Default: 2025.4.0
Description: authentik Docker image tag
AuthentikServerCPU:
Type: Number

Binary file not shown.

View File

@ -12,8 +12,8 @@
# tmassimi, 2024
# Marc Schmitt, 2024
# albanobattistella <albanobattistella@gmail.com>, 2024
# Matteo Piccina <altermatte@gmail.com>, 2025
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
# Matteo Piccina <altermatte@gmail.com>, 2025
#
#, fuzzy
msgid ""
@ -22,7 +22,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
"Last-Translator: Matteo Piccina <altermatte@gmail.com>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -383,7 +383,7 @@ msgstr "Mappatura delle proprietà"
#: authentik/core/models.py
msgid "session data"
msgstr "dati sessione"
msgstr ""
#: authentik/core/models.py
msgid "Session"
@ -509,7 +509,7 @@ msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
msgid "Number of passwords to check against."
msgstr "Numero di password da verificare."
msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
#: authentik/policies/password/models.py
@ -519,19 +519,18 @@ msgstr "Password non impostata nel contesto"
#: authentik/enterprise/policies/unique_password/models.py
msgid "This password has been used previously. Please choose a different one."
msgstr ""
"Questa password è già stata utilizzata in precedenza. Scegline una diversa."
#: authentik/enterprise/policies/unique_password/models.py
msgid "Password Uniqueness Policy"
msgstr "Politica di unicità della password"
msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
msgid "Password Uniqueness Policies"
msgstr "Criteri di unicità delle password"
msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
msgid "User Password History"
msgstr "Cronologia password utente"
msgstr ""
#: authentik/enterprise/policy.py
msgid "Enterprise required to access this feature."
@ -2204,7 +2203,7 @@ msgstr "Ruoli"
#: authentik/rbac/models.py
msgid "Initial Permissions"
msgstr "Permessi Iniziali"
msgstr ""
#: authentik/rbac/models.py
msgid "System permission"
@ -2459,9 +2458,6 @@ msgid ""
"attribute. This allows nested group resolution on systems like FreeIPA and "
"Active Directory"
msgstr ""
"Cerca l'appartenenza al gruppo in base a un attributo utente anziché a un "
"attributo di gruppo. Questo consente la risoluzione di gruppi nidificati su "
"sistemi come FreeIPA e Active Directory."
#: authentik/sources/ldap/models.py
msgid "LDAP Source"
@ -2481,19 +2477,19 @@ msgstr "Mappature delle proprietà della sorgente LDAP"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection"
msgstr "Connessione Sorgente LDAP Utente"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connections"
msgstr "Connessioni Sorgente LDAP Utente"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connection"
msgstr "Connessione Sorgente LDAP Gruppo"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connections"
msgstr "Connessioni Sorgente LDAP Gruppo"
msgstr ""
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
@ -2505,11 +2501,11 @@ msgstr "Nessun token ricevuto."
#: authentik/sources/oauth/models.py
msgid "HTTP Basic Authentication"
msgstr "HTTP Basic Authentication"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Include the client ID and secret as request parameters"
msgstr "Includi il client ID e il segreto come parametri di richiesta"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Request Token URL"
@ -2556,8 +2552,6 @@ msgid ""
"How to perform authentication during an authorization_code token request "
"flow"
msgstr ""
"Come eseguire l'autenticazione durante un flusso di richiesta del token "
"authorization_code"
#: authentik/sources/oauth/models.py
msgid "OAuth Source"
@ -3490,9 +3484,6 @@ msgid ""
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
" to skip straight to entering their password."
msgstr ""
"Mostra all'utente il pulsante \"Ricordami su questo dispositivo\", "
"consentendo agli utenti abituali di passare direttamente all'inserimento "
"della password."
#: authentik/stages/identification/models.py
msgid "Optional enrollment flow, which is linked at the bottom of the page."
@ -3882,11 +3873,11 @@ msgstr ""
#: authentik/tenants/models.py
msgid "Reputation cannot decrease lower than this value. Zero or negative."
msgstr "La reputazione non può scendere sotto questo valore. Zero o negativo."
msgstr ""
#: authentik/tenants/models.py
msgid "Reputation cannot increase higher than this value. Zero or positive."
msgstr "La reputazione non può superare questo valore. Zero o positivo."
msgstr ""
#: authentik/tenants/models.py
msgid "The option configures the footer links on the flow executor pages."

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

538
package-lock.json generated
View File

@ -1,546 +1,12 @@
{
"name": "@goauthentik/authentik",
"version": "2025.4.0",
"version": "2025.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/authentik",
"version": "2025.4.0",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.10",
"typescript": "^5.6.2"
}
},
"node_modules/@babel/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.9",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
"integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/template": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
"integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.27.0",
"@babel/parser": "^7.27.0",
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@pkgr/core": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz",
"integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz",
"integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@babel/generator": "^7.26.5",
"@babel/parser": "^7.26.7",
"@babel/traverse": "^7.26.7",
"@babel/types": "^7.26.7",
"javascript-natural-sort": "^0.7.1",
"lodash": "^4.17.21"
},
"engines": {
"node": ">18.12"
},
"peerDependencies": {
"@vue/compiler-sfc": "3.x",
"prettier": "2.x - 3.x",
"prettier-plugin-svelte": "3.x",
"svelte": "4.x || 5.x"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
},
"svelte": {
"optional": true
}
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/detect-indent": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz",
"integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.20"
}
},
"node_modules/detect-newline": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-4.0.1.tgz",
"integrity": "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/get-stdin": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/git-hooks-list": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-3.2.0.tgz",
"integrity": "sha512-ZHG9a1gEhUMX1TvGrLdyWb9kDopCBbTnI8z4JgRMYxsijWipgjSEYoPWqBuIB0DnRnvqlQSEeVmzpeuPm7NdFQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/fisker/git-hooks-list?sponsor=1"
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
"dev": true,
"license": "MIT"
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": ">=2.0",
"typescript": ">=2.9",
"vue-tsc": "^2.1.0"
},
"peerDependenciesMeta": {
"vue-tsc": {
"optional": true
}
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.10",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.10.tgz",
"integrity": "sha512-LUxATI5YsImIVSaaLJlJ3aE6wTD+nvots18U3GuQMJpUyClChaZlQrqx3dBnbhF20OnKWZyx8EgyZypQtBDtgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "2.15.1",
"synckit": "0.9.2"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
},
"peerDependenciesMeta": {
"prettier": {
"optional": true
}
}
},
"node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sort-object-keys": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz",
"integrity": "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==",
"dev": true,
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.15.1.tgz",
"integrity": "sha512-9x9+o8krTT2saA9liI4BljNjwAbvUnWf11Wq+i/iZt8nl2UGYnf3TH5uBydE7VALmP7AGwlfszuEeL8BDyb0YA==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-indent": "^7.0.1",
"detect-newline": "^4.0.0",
"get-stdin": "^9.0.0",
"git-hooks-list": "^3.0.0",
"is-plain-obj": "^4.1.0",
"semver": "^7.6.0",
"sort-object-keys": "^1.1.3",
"tinyglobby": "^0.2.9"
},
"bin": {
"sort-package-json": "cli.js"
}
},
"node_modules/synckit": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
"version": "2025.2.1"
}
}
}

View File

@ -1,15 +1,5 @@
{
"name": "@goauthentik/authentik",
"version": "2025.4.1",
"private": true,
"type": "module",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.10",
"typescript": "^5.6.2"
},
"workspaces": [],
"prettier": "./packages/prettier-config/index.js"
"version": "2025.4.0",
"private": true
}

View File

@ -1,12 +1,12 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "1.0.6",
"version": "1.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/docusaurus-config",
"version": "1.0.6",
"version": "1.0.5",
"license": "MIT",
"dependencies": {
"deepmerge-ts": "^7.1.5",

View File

@ -1,6 +1,6 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "1.0.6",
"version": "1.0.5",
"description": "authentik's Docusaurus config",
"license": "MIT",
"scripts": {

View File

@ -2,3 +2,4 @@
This package contains utility scripts common to all TypeScript and JavaScript packages in the
`@goauthentik` monorepo.

View File

@ -1,9 +1,8 @@
/**
* @file Constants for JavaScript and TypeScript files.
*
*/
/// <reference types="../../types/global.js" />
/**
* The current Node.js environment, defaulting to "development" when not set.
*
@ -13,4 +12,6 @@
* ensure that module tree-shaking works correctly.
*
*/
export const NodeEnvironment = process.env.NODE_ENV || "development";
export const NodeEnvironment = /** @type {'development' | 'production'} */ (
process.env.NODE_ENV || "development"
);

View File

@ -1,7 +1,4 @@
/// <reference types="./types/global.js" />
export * from "./paths.js";
export * from "./constants.js";
export * from "./build.js";
export * from "./version.js";
export * from "./scripting.js";

View File

@ -0,0 +1,19 @@
{
"name": "@goauthentik/monorepo",
"version": "1.0.0",
"description": "Utilities for the authentik monorepo.",
"private": true,
"license": "MIT",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
}
},
"types": "./out/index.d.ts",
"engines": {
"node": ">=20.11"
}
}

View File

@ -0,0 +1,30 @@
import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* @typedef {'~authentik'} MonoRepoRoot
*/
/**
* The root of the authentik monorepo.
*/
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(__dirname, "..", ".."));
const require = createRequire(import.meta.url);
/**
* Resolve a package name to its location in the monorepo to the single node_modules directory.
* @param {string} packageName
* @returns {string} The resolved path to the package.
* @throws {Error} If the package cannot be resolved.
*/
export function resolvePackage(packageName) {
const packageJSONPath = require.resolve(join(packageName, "package.json"), {
paths: [MonoRepoRoot],
});
return dirname(packageJSONPath);
}

View File

View File

@ -1,6 +1,6 @@
import { execSync } from "node:child_process";
import PackageJSON from "../../../package.json" with { type: "json" };
import PackageJSON from "../../package.json" with { type: "json" };
import { MonoRepoRoot } from "./paths.js";
/**

View File

@ -76,7 +76,6 @@ EXPOSE 9000 9300 9443
USER 1000
ENV TMPDIR=/dev/shm/ \
GOFIPS=1
ENV GOFIPS=1
ENTRYPOINT ["/proxy"]

View File

@ -1,116 +1,104 @@
[project]
name = "authentik"
version = "2025.4.1"
version = "2025.4.0"
description = ""
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
requires-python = "==3.13.*"
requires-python = "==3.12.*"
dependencies = [
"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.2",
"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",
"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 ==0.6.0",
"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 <= 1.3.14",
"zxcvbn",
]
[dependency-groups]
dev = [
"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]
no-binary-package = [
# This differs from the no-binary packages in the Dockerfile. This is due to the fact
# that these packages are built from source for different reasons than cryptography and kadmin.
# These packages are built from source to link against the libxml2 on the system which is
# required for functionality and to stay up-to-date on both libraries.
# The other packages specified in the dockerfile are compiled from source to link against the
# correct FIPS OpenSSL libraries
"lxml",
"xmlsec",
"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",
]
[tool.uv.sources]
@ -155,12 +143,12 @@ ignore-words = ".github/codespell-words.txt"
[tool.black]
line-length = 100
target-version = ['py313']
target-version = ['py312']
exclude = 'node_modules'
[tool.ruff]
line-length = 100
target-version = "py313"
target-version = "py312"
exclude = ["**/migrations/**", "**/node_modules/**"]
[tool.ruff.lint]

View File

@ -56,7 +56,6 @@ HEALTHCHECK --interval=5s --retries=20 --start-period=3s CMD [ "/rac", "healthch
USER 1000
ENV TMPDIR=/dev/shm/ \
GOFIPS=1
ENV GOFIPS=1
ENTRYPOINT ["/rac"]

View File

@ -56,7 +56,6 @@ EXPOSE 1812/udp 9300
USER 1000
ENV TMPDIR=/dev/shm/ \
GOFIPS=1
ENV GOFIPS=1
ENTRYPOINT ["/radius"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2025.4.1
version: 2025.4.0
description: Making authentication simple.
contact:
email: hello@goauthentik.io

View File

@ -1,12 +1,12 @@
services:
chrome:
image: docker.io/selenium/standalone-chrome:136.0
image: docker.io/selenium/standalone-chrome:122.0
volumes:
- /dev/shm:/dev/shm
network_mode: host
restart: always
mailpit:
image: docker.io/axllent/mailpit:v1.24.2
image: docker.io/axllent/mailpit:v1.6.5
ports:
- 1025:1025
- 8025:8025

View File

@ -1,19 +1,12 @@
"""test default login flow"""
from authentik.blueprints.tests import apply_blueprint
from authentik.flows.models import Flow
from tests.e2e.utils import SeleniumTestCase, retry
class TestFlowsLogin(SeleniumTestCase):
"""test default login flow"""
def tearDown(self):
# Reset authentication flow's compatibility mode; we need to do this as its
# not specified in the blueprint
Flow.objects.filter(slug="default-authentication-flow").update(compatibility_mode=False)
return super().tearDown()
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
@ -30,21 +23,3 @@ class TestFlowsLogin(SeleniumTestCase):
self.login()
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(self.user)
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_login_compatibility_mode(self):
"""test default login flow with compatibility mode enabled"""
Flow.objects.filter(slug="default-authentication-flow").update(compatibility_mode=True)
self.driver.get(
self.url(
"authentik_core:if-flow",
flow_slug="default-authentication-flow",
)
)
self.login(shadow_dom=False)
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(self.user)

View File

@ -1,51 +0,0 @@
"""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

@ -26,10 +26,8 @@ from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.command import Command
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from structlog.stdlib import get_logger
@ -38,8 +36,8 @@ from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user
from authentik.lib.generators import generate_id
RETRIES = int(environ.get("RETRIES", "3"))
IS_CI = "CI" in environ
RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1
def get_docker_tag() -> str:
@ -199,12 +197,7 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
super().tearDown()
if IS_CI:
print("::group::Browser logs")
# Very verbose way to get browser logs
# https://github.com/SeleniumHQ/selenium/pull/15641
# for some reason this removes the `get_log` API from Remote Webdriver
# and only keeps it on the local Chrome web driver, even when using
# a remote chrome driver...? (nvm the fact this was released as a minor version)
for line in self.driver.execute(Command.GET_LOG, {"type": "browser"})["value"]:
for line in self.driver.get_log("browser"):
print(line["message"])
if IS_CI:
print("::endgroup::")
@ -241,30 +234,10 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
element = self.driver.execute_script("return arguments[0].shadowRoot", shadow_root)
return element
def shady_dom(self) -> WebElement:
class wrapper:
def __init__(self, container: WebDriver):
self.container = container
def find_element(self, by: str, selector: str) -> WebElement:
return self.container.execute_script(
"return document.__shady_native_querySelector(arguments[0])", selector
)
return wrapper(self.driver)
def login(self, shadow_dom=True):
"""Do entire login flow"""
if shadow_dom:
flow_executor = self.get_shadow_root("ak-flow-executor")
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
else:
flow_executor = self.shady_dom()
identification_stage = self.shady_dom()
wait = WebDriverWait(identification_stage, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=uidField]")))
def login(self):
"""Do entire login flow and check user afterwards"""
flow_executor = self.get_shadow_root("ak-flow-executor")
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").click()
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").send_keys(
@ -274,16 +247,8 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
Keys.ENTER
)
if shadow_dom:
flow_executor = self.get_shadow_root("ak-flow-executor")
password_stage = self.get_shadow_root("ak-stage-password", flow_executor)
else:
flow_executor = self.shady_dom()
password_stage = self.shady_dom()
wait = WebDriverWait(password_stage, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=password]")))
flow_executor = self.get_shadow_root("ak-flow-executor")
password_stage = self.get_shadow_root("ak-stage-password", flow_executor)
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
self.user.username
)

View File

@ -1,28 +0,0 @@
// TypeScript Project Configuration
{
"extends": "./packages/tsconfig/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
},
"watchOptions": {
"excludeDirectories": [
"**/.git", // Git
"**/.yarn", // Yarn
"**/.vscode", // VS Code
"**/.vscode-test-web", // VS Code Web Test
"**/dist", // Distributed build files
"**/out", // Output build files
"**/.drafts", // Drafts
"**/.github", // GitHub
"**/node_modules" // Node modules
]
},
// The root project has no sources of its own. By setting `files` to an empty
// list, TS won't automatically include all sources below root (the default).
"files": [],
"references": [
// Note that references are in the order we want them to be built.
// TODO: Left blank until TypeScript workspaces are complete.
]
}

2192
uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,15 @@
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
out
# don't lint nyc coverage output
coverage
# Import order matters
poly.ts
src/locale-codes.ts
src/locales/
storybook-static/
# Prettier breaks the tsconfig file
tsconfig.json
.storybook/css-import-maps*
package.json
packages/**/package.json

View File

@ -0,0 +1,11 @@
import { create } from "@storybook/theming/create";
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
export default create({
base: isDarkMode ? "dark" : "light",
brandTitle: "authentik Storybook",
brandUrl: "https://goauthentik.io",
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
brandTarget: "_self",
});

View File

@ -1,69 +0,0 @@
/**
* @file Storybook configuration.
* @import { StorybookConfig } from "@storybook/web-components-vite";
* @import { InlineConfig, Plugin } from "vite";
*/
import { cwd } from "process";
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
const NODE_ENV = process.env.NODE_ENV || "development";
const CSSImportPattern = /import [\w\$]+ from .+\.(css)/g;
const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/;
/**
* @satisfies {Plugin<never>}
*/
const inlineCSSPlugin = {
name: "inline-css-plugin",
transform: (source, id) => {
if (!JavaScriptFilePattern.test(id)) return;
const code = source.replace(CSSImportPattern, (match) => {
return `${match}?inline`;
});
return {
code,
};
},
};
/**
* @satisfies {StorybookConfig}
*/
const config = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-controls",
"@storybook/addon-links",
"@storybook/addon-essentials",
"storybook-addon-mock",
],
framework: {
name: "@storybook/web-components-vite",
options: {},
},
docs: {
autodocs: "tag",
},
viteFinal({ plugins = [], ...config }) {
/**
* @satisfies {InlineConfig}
*/
const mergedConfig = {
...config,
define: {
"process.env.NODE_ENV": JSON.stringify(NODE_ENV),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(process.env.AK_API_BASE_PATH || ""),
},
plugins: [inlineCSSPlugin, ...plugins, postcssLit(), tsconfigPaths()],
};
return mergedConfig;
},
};
export default config;

81
web/.storybook/main.ts Normal file
View File

@ -0,0 +1,81 @@
import replace from "@rollup/plugin-replace";
import type { StorybookConfig } from "@storybook/web-components-vite";
import { cwd } from "process";
import modify from "rollup-plugin-modify";
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
export const isProdBuild = process.env.NODE_ENV === "production";
export const apiBasePath = process.env.AK_API_BASE_PATH || "";
const importInlinePatterns = [
'import AKGlobal from "(\\.\\./)*common/styles/authentik\\.css',
'import AKGlobal from "@goauthentik/common/styles/authentik\\.css',
'import PF.+ from "@patternfly/patternfly/\\S+\\.css',
'import ThemeDark from "@goauthentik/common/styles/theme-dark\\.css',
'import OneDark from "@goauthentik/common/styles/one-dark\\.css',
'import styles from "\\./LibraryPageImpl\\.css',
];
const importInlineRegexp = new RegExp(importInlinePatterns.map((a) => `(${a})`).join("|"));
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-controls",
"@storybook/addon-links",
"@storybook/addon-essentials",
"storybook-addon-mock",
],
staticDirs: [
{
from: "../node_modules/@patternfly/patternfly/patternfly-base.css",
to: "@patternfly/patternfly/patternfly-base.css",
},
{
from: "../src/common/styles/authentik.css",
to: "@goauthentik/common/styles/authentik.css",
},
{
from: "../src/common/styles/theme-dark.css",
to: "@goauthentik/common/styles/theme-dark.css",
},
{
from: "../src/common/styles/one-dark.css",
to: "@goauthentik/common/styles/one-dark.css",
},
],
framework: {
name: "@storybook/web-components-vite",
options: {},
},
docs: {
autodocs: "tag",
},
async viteFinal(config) {
return {
...config,
plugins: [
modify({
find: importInlineRegexp,
replace: (match: RegExpMatchArray) => {
return `${match}?inline`;
},
}),
replace({
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development",
),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
"preventAssignment": true,
}),
...config.plugins,
postcssLit(),
tsconfigPaths(),
],
};
},
};
export default config;

View File

@ -1,38 +0,0 @@
/**
* @file Storybook manager configuration.
*
* @import { ThemeVarsPartial } from "storybook/internal/theming";
*/
import { createUIThemeEffect, resolveUITheme } from "@goauthentik/web/common/theme.ts";
import { addons } from "@storybook/manager-api";
import { create } from "@storybook/theming/create";
/**
* @satisfies {Partial<ThemeVarsPartial>}
*/
const baseTheme = {
brandTitle: "authentik Storybook",
brandUrl: "https://goauthentik.io",
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
brandTarget: "_self",
};
const uiTheme = resolveUITheme();
addons.setConfig({
theme: create({
...baseTheme,
base: uiTheme,
}),
enableShortcuts: false,
});
createUIThemeEffect((nextUITheme) => {
addons.setConfig({
theme: create({
...baseTheme,
base: nextUITheme,
}),
enableShortcuts: false,
});
});

View File

@ -0,0 +1,9 @@
// .storybook/manager.js
import { addons } from "@storybook/manager-api";
import authentikTheme from "./authentikTheme";
addons.setConfig({
theme: authentikTheme,
enableShortcuts: false,
});

View File

@ -1,3 +1,5 @@
<link rel="stylesheet" href="@patternfly/patternfly/patternfly-base.css" />
<link rel="stylesheet" href="@goauthentik/common/styles/authentik.css" />
<style>
body {
overflow-y: scroll;

View File

@ -1,32 +0,0 @@
/// <reference types="../types/css.js" />
/**
* @file Storybook manager configuration.
*
* @import { Preview } from "@storybook/web-components";
*/
import { applyDocumentTheme } from "@goauthentik/web/common/theme.ts";
applyDocumentTheme();
/**
* @satisfies {Preview}
*/
const preview = {
parameters: {
options: {
storySort: {
method: "alphabetical",
},
},
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;

30
web/.storybook/preview.ts Normal file
View File

@ -0,0 +1,30 @@
import type { Preview } from "@storybook/web-components";
import "@goauthentik/common/styles/authentik.css";
// import "@goauthentik/common/styles/theme-dark.css";
import "@patternfly/patternfly/components/Brand/brand.css";
import "@patternfly/patternfly/components/Page/page.css";
// .storybook/preview.js
import "@patternfly/patternfly/patternfly-base.css";
const preview: Preview = {
parameters: {
options: {
storySort: {
method: "alphabetical",
},
},
actions: { argTypesRegex: "^on[A-Z].*" },
cssUserPrefs: {
"prefers-color-scheme": "light",
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;

3529
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +1,6 @@
{
"name": "@goauthentik/web",
"version": "0.0.0",
"license": "MIT",
"private": true,
"scripts": {
"build": "wireit",
"build-locales": "wireit",
"build-locales:build": "wireit",
"build-proxy": "wireit",
"build:sfe": "wireit",
"esbuild:watch": "node scripts/build-web.mjs --watch",
"extract-locales": "wireit",
"format": "wireit",
"lint": "wireit",
"lint:imports": "wireit",
"lint:lockfile": "wireit",
"lint:nightmare": "wireit",
"lint:precommit": "wireit",
"lint:types": "wireit",
"lit-analyse": "wireit",
"precommit": "wireit",
"prettier": "wireit",
"prettier-check": "wireit",
"pseudolocalize": "wireit",
"storybook": "storybook dev -p 6006",
"storybook:build": "wireit",
"test": "wireit",
"test:e2e": "wireit",
"test:e2e:watch": "wireit",
"test:watch": "wireit",
"tsc": "wireit",
"watch": "run-s build-locales esbuild:watch"
},
"type": "module",
"exports": {
"./package.json": "./package.json",
"./paths": "./paths.js",
"./scripts/*": "./scripts/*.mjs",
"./elements/*": "./src/elements/*",
"./common/*": "./src/common/*",
"./components/*": "./src/components/*",
"./flow/*": "./src/flow/*",
"./locales/*": "./src/locales/*",
"./user/*": "./src/user/*",
"./admin/*": "./src/admin/*"
},
"dependencies": {
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9",
@ -56,7 +12,8 @@
"@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2025.4.1-1747332783",
"@goauthentik/api": "^2025.4.0-1746018955",
"@lit-labs/ssr": "3.2.2",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
"@lit/reactive-element": "^2.0.4",
@ -97,7 +54,6 @@
"remark-gfm": "^4.0.1",
"remark-mdx-frontmatter": "^5.0.0",
"style-mod": "^4.1.2",
"trusted-types": "^2.0.0",
"ts-pattern": "^5.4.0",
"unist-util-visit": "^5.0.0",
"webcomponent-qr-code": "^1.2.0",
@ -106,20 +62,19 @@
"devDependencies": {
"@eslint/js": "^9.11.1",
"@goauthentik/esbuild-plugin-live-reload": "^1.0.4",
"@goauthentik/monorepo": "^1.0.0",
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@hcaptcha/types": "^1.0.4",
"@lit/localize-tools": "^0.8.0",
"@rollup/plugin-replace": "^6.0.1",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-links": "^8.6.12",
"@storybook/blocks": "^8.6.12",
"@storybook/experimental-addon-test": "^8.6.12",
"@storybook/manager-api": "^8.6.12",
"@storybook/test": "^8.6.12",
"@storybook/web-components": "^8.6.12",
"@storybook/web-components-vite": "^8.6.12",
"@storybook/addon-essentials": "^8.3.4",
"@storybook/addon-links": "^8.3.4",
"@storybook/api": "^7.6.17",
"@storybook/blocks": "^8.3.4",
"@storybook/builder-vite": "^8.3.4",
"@storybook/manager-api": "^8.3.4",
"@storybook/web-components": "^8.3.4",
"@storybook/web-components-vite": "^8.3.4",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/chart.js": "^2.9.41",
"@types/codemirror": "^5.60.15",
@ -138,22 +93,24 @@
"@wdio/spec-reporter": "^9.1.2",
"chromedriver": "^131.0.1",
"esbuild": "^0.25.0",
"esbuild-plugin-copy": "^2.1.1",
"esbuild-plugin-polyfill-node": "^0.3.0",
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
"eslint": "^9.11.1",
"eslint-plugin-lit": "^1.15.0",
"eslint-plugin-wc": "^2.1.1",
"github-slugger": "^2.0.0",
"glob": "^11.0.0",
"globals": "^15.10.0",
"knip": "^5.30.6",
"lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"pseudolocale": "^2.1.0",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^8.6.12",
"storybook": "^8.3.4",
"storybook-addon-mock": "^5.0.0",
"syncpack": "^13.0.0",
"turnstile-types": "^1.2.3",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0",
@ -161,6 +118,10 @@
"vite-tsconfig-paths": "^5.0.1",
"wireit": "^0.14.9"
},
"engines": {
"node": ">=20"
},
"license": "MIT",
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.24.0",
"@esbuild/linux-amd64": "^0.18.11",
@ -169,6 +130,48 @@
"@rollup/rollup-linux-arm64-gnu": "4.23.0",
"@rollup/rollup-linux-x64-gnu": "4.23.0"
},
"overrides": {
"rapidoc": {
"@apitools/openapi-parser@": "0.0.37"
},
"chromedriver": {
"axios": "^1.8.4"
}
},
"prettier": "@goauthentik/prettier-config",
"private": true,
"scripts": {
"build": "wireit",
"build-locales": "wireit",
"build-locales:build": "wireit",
"build-proxy": "wireit",
"build:sfe": "wireit",
"esbuild:watch": "node scripts/build-web.mjs --watch",
"extract-locales": "wireit",
"format": "wireit",
"lint": "wireit",
"lint:imports": "wireit",
"lint:lockfile": "wireit",
"lint:nightmare": "wireit",
"lint:package": "wireit",
"lint:precommit": "wireit",
"lint:types": "wireit",
"lit-analyse": "wireit",
"postinstall": "bash scripts/patch-spotlight.sh",
"precommit": "wireit",
"prettier": "wireit",
"prettier-check": "wireit",
"pseudolocalize": "wireit",
"storybook": "storybook dev -p 6006",
"storybook:build": "wireit",
"test": "wireit",
"test:e2e": "wireit",
"test:e2e:watch": "wireit",
"test:watch": "wireit",
"tsc": "wireit",
"watch": "run-s build-locales esbuild:watch"
},
"type": "module",
"wireit": {
"build": {
"#comment": [
@ -245,7 +248,10 @@
"command": "lit-localize extract"
},
"format": {
"command": "prettier --write ."
"command": "prettier --write .",
"dependencies": [
"lint:package"
]
},
"format:packages": {
"dependencies": [
@ -284,6 +290,9 @@
"./packages/sfe:lint:lockfile"
]
},
"lint:package": {
"command": "syncpack format -i ' '"
},
"lint:nightmare": {
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --nightmare",
"env": {
@ -314,6 +323,7 @@
"lint:types",
"lint:components",
"lint:spelling",
"lint:package",
"lint:lockfile",
"lint:lockfiles",
"lint:precommit",
@ -378,20 +388,8 @@
]
}
},
"engines": {
"node": ">=20"
},
"workspaces": [
".",
"./packages/*"
],
"prettier": "@goauthentik/prettier-config",
"overrides": {
"rapidoc": {
"@apitools/openapi-parser@": "0.0.37"
},
"chromedriver": {
"axios": "^1.8.4"
}
}
]
}

View File

@ -6,7 +6,7 @@
* @import { Message as ESBuildMessage } from "esbuild";
*/
const logPrefix = "authentik/dev/web: ";
const logPrefix = "👷 [ESBuild]";
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

@ -1,11 +1,22 @@
{
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.0.4",
"description": "ESBuild plugin to watch for file changes and trigger client-side reloads.",
"license": "MIT",
"private": true,
"main": "index.js",
"type": "module",
"version": "1.0.4",
"dependencies": {
"find-free-ports": "^3.1.1"
},
"devDependencies": {
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/node": "^22.14.1",
"esbuild": "^0.25.0",
"prettier": "^3.3.3",
"typescript": "^5.6.2"
},
"engines": {
"node": ">=20.11"
},
"exports": {
"./package.json": "./package.json",
".": {
@ -21,33 +32,22 @@
"import": "./plugin/index.js"
}
},
"dependencies": {
"find-free-ports": "^3.1.1"
},
"devDependencies": {
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/node": "^22.14.1",
"esbuild": "^0.25.0",
"prettier": "^3.3.3",
"typescript": "^5.6.2"
},
"peerDependencies": {
"esbuild": "^0.25.0"
},
"engines": {
"node": ">=20.11"
},
"types": "./out/index.d.ts",
"files": [
"./index.js",
"client/**/*",
"plugin/**/*",
"out/**/*"
],
"license": "MIT",
"main": "index.js",
"peerDependencies": {
"esbuild": "^0.25.0"
},
"prettier": "@goauthentik/prettier-config",
"private": true,
"publishConfig": {
"access": "public"
}
},
"type": "module",
"types": "./out/index.d.ts"
}

View File

@ -1,42 +0,0 @@
/**
* @file Utility functions for building and copying files.
*/
/**
* A source environment variable, which can be a string, number, boolean, null, or undefined.
* @typedef {string | number | boolean | null | undefined} EnvironmentVariable
*/
/**
* A type helper for serializing environment variables.
*
* @template {EnvironmentVariable} T
* @typedef {T extends string ? `"${T}"` : T} JSONify
*/
/**
* Given an object of environment variables, returns a new object with the same keys and values, but
* with the values serialized as strings.
*
* @template {Record<string, EnvironmentVariable>} EnvRecord
* @template {string} [Prefix='process.env.']
*
* @param {EnvRecord} input
* @param {Prefix} [prefix='process.env.']
*
* @returns {{[K in keyof EnvRecord as `${Prefix}${K}`]: JSONify<EnvRecord[K]>}}
*/
export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ ("process.env.")) {
/**
* @type {Record<string, string>}
*/
const env = {};
for (const [key, value] of Object.entries(input)) {
const namespaceKey = prefix + key;
env[namespaceKey] = JSON.stringify(value || "");
}
return /** @type {any} */ (env);
}

View File

@ -1,28 +0,0 @@
{
"name": "@goauthentik/monorepo",
"version": "1.0.0",
"description": "Utilities for the authentik monorepo.",
"license": "MIT",
"private": true,
"main": "index.js",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./out/index.d.ts",
"import": "./index.js"
}
},
"devDependencies": {
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@types/node": "^22.14.1",
"prettier": "^3.3.3",
"typescript": "^5.6.2"
},
"engines": {
"node": ">=20.11"
},
"types": "./out/index.d.ts",
"prettier": "@goauthentik/prettier-config"
}

View File

@ -1,45 +0,0 @@
import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const relativeDirname = dirname(fileURLToPath(import.meta.url));
/**
* @typedef {'~authentik'} MonoRepoRoot
*/
/**
* The root of the authentik monorepo.
*/
// TODO: Revise when this package is moved to the monorepo's `packages/monorepo` directory.
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (
resolve(relativeDirname, "..", "..", "..")
);
const require = createRequire(import.meta.url);
/**
* Resolve a package name to its location in the monorepo to the single node_modules directory.
* @param {string} packageName
*
* @returns {string} The resolved path to the package.
* @throws {Error} If the package cannot be resolved.
*/
export function resolvePackage(packageName) {
const relativePackageJSONPath = join(packageName, "package.json");
/** @type {string} */
let absolutePackageJSONPath;
try {
absolutePackageJSONPath = require.resolve(relativePackageJSONPath);
} catch (cause) {
const error = new Error(`Failed to resolve package "${packageName}"`);
error.cause = cause;
throw error;
}
return dirname(absolutePackageJSONPath);
}

View File

@ -1,15 +0,0 @@
declare module "process" {
global {
namespace NodeJS {
interface ProcessEnv {
/**
* An environment variable used to determine
* whether Node.js is running in production mode.
*
* @see {@link https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production | The difference between development and production}
*/
NODE_ENV?: "production" | "development";
}
}
}
}

View File

@ -47,16 +47,7 @@ class SimpleFlowExecutor {
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
}
loading() {
this.container.innerHTML = `<div class="d-flex justify-content-center">
<div class="spinner-border spinner-border-md" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>`;
}
start() {
this.loading();
$.ajax({
type: "GET",
url: this.apiURL,
@ -210,9 +201,6 @@ 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,78 +0,0 @@
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const relativeDirname = dirname(fileURLToPath(import.meta.url));
//#region Base paths
/**
* @typedef {'@goauthentik/web'} WebPackageIdentifier
*/
/**
* The root of the web package.
*/
export const PackageRoot = /** @type {WebPackageIdentifier} */ (resolve(relativeDirname));
/**
* The name of the distribution directory.
*/
export const DistDirectoryName = "dist";
/**
* Path to the web package's distribution directory.
*
* This is where the built files are located after running the build process.
*/
export const DistDirectory = /** @type {`${WebPackageIdentifier}/${DistDirectoryName}`} */ (
resolve(relativeDirname, DistDirectoryName)
);
//#endregion
//#region Entry points
/**
* @typedef {{ in: string, out: string }} EntryPointTarget
*
* ESBuild entrypoint target.
* Matches the type defined in the ESBuild context.
*/
/**
* Entry points available for building.
*
* @satisfies {Record<string, EntryPointTarget>}
*/
export const EntryPoint = /** @type {const} */ ({
Admin: {
in: resolve(PackageRoot, "src", "admin", "AdminInterface", "index.entrypoint.ts"),
out: resolve(DistDirectory, "admin", "AdminInterface"),
},
User: {
in: resolve(PackageRoot, "src", "user", "index.entrypoint.ts"),
out: resolve(DistDirectory, "user", "UserInterface"),
},
Flow: {
in: resolve(PackageRoot, "src", "flow", "index.entrypoint.ts"),
out: resolve(DistDirectory, "flow", "FlowInterface"),
},
Standalone: {
in: resolve(PackageRoot, "src", "standalone", "api-browser/index.entrypoint.ts"),
out: resolve(DistDirectory, "standalone", "api-browser", "index"),
},
StandaloneLoading: {
in: resolve(PackageRoot, "src", "standalone", "loading/index.entrypoint.ts"),
out: resolve(DistDirectory, "standalone", "loading", "index"),
},
RAC: {
in: resolve(PackageRoot, "src", "rac", "index.entrypoint.ts"),
out: resolve(DistDirectory, "rac", "index"),
},
Polyfill: {
in: resolve(PackageRoot, "src", "polyfill", "index.entrypoint.ts"),
out: resolve(DistDirectory, "poly"),
},
});
//#endregion

View File

@ -4,86 +4,138 @@
* @import { BuildOptions } from "esbuild";
*/
import { liveReloadPlugin } from "@goauthentik/esbuild-plugin-live-reload/plugin";
import {
MonoRepoRoot,
NodeEnvironment,
readBuildIdentifier,
resolvePackage,
serializeEnvironmentVars,
} from "@goauthentik/monorepo";
import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths";
import { execFileSync } from "child_process";
import { deepmerge } from "deepmerge-ts";
import esbuild from "esbuild";
import copy from "esbuild-plugin-copy";
import { polyfillNode } from "esbuild-plugin-polyfill-node";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs";
import { globSync } from "glob";
import * as path from "path";
import { cwd } from "process";
import process from "process";
import { fileURLToPath } from "url";
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
const logPrefix = "[Build]";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
let authentikProjectRoot = path.join(__dirname, "..", "..");
const definitions = serializeEnvironmentVars({
NODE_ENV: NodeEnvironment,
CWD: process.cwd(),
AK_API_BASE_PATH: process.env.AK_API_BASE_PATH,
});
try {
// Use the package.json file in the root folder, as it has the current version information.
authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
encoding: "utf8",
}).replace("\n", "");
} catch (_error) {
// We probably don't have a .git folder, which could happen in container builds.
}
const patternflyPath = resolvePackage("@patternfly/patternfly");
const packageJSONPath = path.join(authentikProjectRoot, "./package.json");
const rootPackage = JSON.parse(readFileSync(packageJSONPath, "utf8"));
const NODE_ENV = process.env.NODE_ENV || "development";
const AK_API_BASE_PATH = process.env.AK_API_BASE_PATH || "";
const environmentVars = new Map([
["NODE_ENV", NODE_ENV],
["CWD", cwd()],
["AK_API_BASE_PATH", AK_API_BASE_PATH],
]);
const definitions = Object.fromEntries(
Array.from(environmentVars).map(([key, value]) => {
return [`process.env.${key}`, JSON.stringify(value)];
}),
);
/**
* @type {Readonly<BuildOptions>}
* All is magic is just to make sure the assets are copied into the right places. This is a very
* stripped down version of what the rollup-copy-plugin does, without any of the features we don't
* use, and using globSync instead of globby since we already had globSync lying around thanks to
* Typescript. If there's a third argument in an array entry, it's used to replace the internal path
* before concatenating it all together as the destination target.
* @type {Array<[string, string, string?]>}
*/
const assetsFileMappings = [
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
["src/common/styles/**", "."],
["src/assets/images/**", "./assets/images"],
["./icons/*", "./assets/icons"],
];
/**
* @param {string} filePath
*/
const isFile = (filePath) => statSync(filePath).isFile();
/**
* @param {string} src Source file
* @param {string} dest Destination folder
* @param {string} [strip] Path to strip from the source file
*/
function nameCopyTarget(src, dest, strip) {
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
return [src, target];
}
for (const [source, rawdest, strip] of assetsFileMappings) {
const matchedPaths = globSync(source);
const dest = path.join("dist", rawdest);
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
for (const [src, dest] of copyTargets) {
if (isFile(src)) {
mkdirSync(path.dirname(dest), { recursive: true });
copyFileSync(src, dest);
}
}
}
/**
* @typedef {[source: string, destination: string]} EntryPoint
*/
/**
* This starts the definitions used for esbuild: Our targets, our arguments, the function for
* running a build, and three options for building: watching, building, and building the proxy.
* Ordered by largest to smallest interface to build even faster
*
* @type {EntryPoint[]}
*/
const entryPoints = [
["admin/AdminInterface/AdminInterface.ts", "admin"],
["user/UserInterface.ts", "user"],
["flow/FlowInterface.ts", "flow"],
["standalone/api-browser/index.ts", "standalone/api-browser"],
["rac/index.ts", "rac"],
["standalone/loading/index.ts", "standalone/loading"],
["polyfill/poly.ts", "."],
];
/**
* @type {import("esbuild").BuildOptions}
*/
const BASE_ESBUILD_OPTIONS = {
entryNames: `[dir]/[name]-${readBuildIdentifier()}`,
chunkNames: "[dir]/chunks/[name]-[hash]",
assetNames: "assets/[dir]/[name]-[hash]",
publicPath: path.join("/static", DistDirectoryName),
outdir: DistDirectory,
bundle: true,
write: true,
sourcemap: true,
minify: NodeEnvironment === "production",
legalComments: "external",
minify: NODE_ENV === "production",
splitting: true,
treeShaking: true,
external: ["*.woff", "*.woff2"],
tsconfig: path.resolve(PackageRoot, "tsconfig.build.json"),
tsconfig: path.resolve(__dirname, "..", "tsconfig.build.json"),
loader: {
".css": "text",
},
plugins: [
copy({
assets: [
{
from: path.join(patternflyPath, "patternfly.min.css"),
to: ".",
},
{
from: path.join(patternflyPath, "assets", "**"),
to: "./assets",
},
{
from: path.resolve(PackageRoot, "src", "common", "styles", "**"),
to: ".",
},
{
from: path.resolve(PackageRoot, "src", "assets", "images", "**"),
to: "./assets/images",
},
{
from: path.resolve(PackageRoot, "icons", "*"),
to: "./assets/icons",
},
],
}),
polyfillNode({
polyfills: {
path: true,
},
}),
mdxPlugin({
root: MonoRepoRoot,
root: authentikProjectRoot,
}),
],
define: definitions,
@ -99,43 +151,69 @@ const BASE_ESBUILD_OPTIONS = {
},
};
async function cleanDistDirectory() {
const timerLabel = `${logPrefix} ♻️ Cleaning previous builds...`;
/**
* Creates a version ID for the build.
* @returns {string}
*/
function composeVersionID() {
const { version } = rootPackage;
const buildHash = process.env.GIT_BUILD_HASH;
console.time(timerLabel);
if (buildHash) {
return `${version}+${buildHash}`;
}
await fs.rm(DistDirectory, {
recursive: true,
force: true,
});
await fs.mkdir(DistDirectory, {
recursive: true,
});
console.timeEnd(timerLabel);
return version;
}
/**
* Creates an ESBuild options, extending the base options with the given overrides.
* Build a single entry point.
*
* @param {BuildOptions} overrides
* @returns {BuildOptions}
* @param {EntryPoint} buildTarget
* @param {Partial<esbuild.BuildOptions>} [overrides]
* @throws {Error} on build failure
*/
export function createESBuildOptions(overrides) {
/**
* @type {BuildOptions}
*/
const mergedOptions = deepmerge(BASE_ESBUILD_OPTIONS, overrides);
function createEntryPointOptions([source, dest], overrides = {}) {
const outdir = path.join(__dirname, "..", "dist", dest);
return mergedOptions;
/**
* @type {esbuild.BuildOptions}
*/
const entryPointConfig = {
entryPoints: [`./src/${source}`],
entryNames: `[dir]/[name]-${composeVersionID()}`,
publicPath: path.join("/static", "dist", dest),
outdir,
};
/**
* @type {esbuild.BuildOptions}
*/
const mergedConfig = deepmerge(BASE_ESBUILD_OPTIONS, entryPointConfig, overrides);
return mergedConfig;
}
/**
* Build all entry points in parallel.
*
* @param {EntryPoint[]} entryPoints
* @returns {Promise<esbuild.BuildResult[]>}
*/
async function buildParallel(entryPoints) {
return Promise.all(
entryPoints.map((entryPoint) => {
return esbuild.build(createEntryPointOptions(entryPoint));
}),
);
}
function doHelp() {
console.log(`Build the authentik UI
options:
-w, --watch: Build all interfaces
-w, --watch: Build all ${entryPoints.length} interfaces
-p, --proxy: Build only the polyfills and the loading application
-h, --help: This help message
`);
@ -144,29 +222,27 @@ function doHelp() {
}
async function doWatch() {
console.group(`${logPrefix} 🤖 Watching entry points`);
console.log("Watching all entry points...");
const entryPoints = Object.entries(EntryPoint).map(([entrypointID, target]) => {
console.log(entrypointID);
const buildContexts = await Promise.all(
entryPoints.map((entryPoint) => {
return esbuild.context(
createEntryPointOptions(entryPoint, {
define: definitions,
plugins: [
liveReloadPlugin({
logPrefix: `Build Observer (${entryPoint[1]})`,
relativeRoot: path.join(__dirname, ".."),
}),
],
}),
);
}),
);
return target;
});
await Promise.all(buildContexts.map((context) => context.rebuild()));
console.groupEnd();
const buildOptions = createESBuildOptions({
entryPoints,
plugins: [
liveReloadPlugin({
relativeRoot: PackageRoot,
}),
],
});
const buildContext = await esbuild.context(buildOptions);
await buildContext.rebuild();
await buildContext.watch();
await Promise.allSettled(buildContexts.map((context) => context.watch()));
return /** @type {Promise<void>} */ (
new Promise((resolve) => {
@ -178,34 +254,15 @@ async function doWatch() {
}
async function doBuild() {
console.group(`${logPrefix} 🚀 Building entry points:`);
console.log("Building all entry points");
const entryPoints = Object.entries(EntryPoint).map(([entrypointID, target]) => {
console.log(entrypointID);
return target;
});
console.groupEnd();
const buildOptions = createESBuildOptions({
entryPoints,
});
await esbuild.build(buildOptions);
console.log("Build complete");
return buildParallel(entryPoints);
}
async function doProxy() {
const entryPoints = [EntryPoint.StandaloneLoading];
const buildOptions = createESBuildOptions({
entryPoints,
});
await esbuild.build(buildOptions);
console.log("Proxy build complete");
return buildParallel(
entryPoints.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
);
}
async function delegateCommand() {
@ -227,16 +284,12 @@ async function delegateCommand() {
}
}
await cleanDistDirectory()
// ---
.then(() =>
delegateCommand()
.then(() => {
console.log("Build complete");
process.exit(0);
})
.catch((error) => {
console.error(error);
process.exit(1);
}),
);
await delegateCommand()
.then(() => {
console.log("Build complete");
process.exit(0);
})
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,33 @@
#!/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

@ -1,11 +1,11 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
import { DefaultBrand } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/EmptyState";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import { msg } from "@lit/localize";
import { TemplateResult, css, html } from "lit";

View File

@ -9,12 +9,8 @@ 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 { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
import { SidebarToggleEventDetail } from "@goauthentik/elements/PageHeader";
import "@goauthentik/elements/ak-locale-context";
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
import "@goauthentik/elements/banner/VersionBanner";
import "@goauthentik/elements/banner/VersionBanner";
import "@goauthentik/elements/messages/MessageContainer";
import "@goauthentik/elements/messages/MessageContainer";
@ -25,124 +21,79 @@ import "@goauthentik/elements/router/RouterOutlet";
import "@goauthentik/elements/sidebar/Sidebar";
import "@goauthentik/elements/sidebar/SidebarItem";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, eventOptions, property, query } from "lit/decorators.js";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
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 { LicenseSummaryStatusEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
import { SessionUser, UiThemeEnum } from "@goauthentik/api";
import {
AdminSidebarEnterpriseEntries,
AdminSidebarEntries,
renderSidebarItems,
} from "./AdminSidebar.js";
import "./AdminSidebar";
if (process.env.NODE_ENV === "development") {
await import("@goauthentik/esbuild-plugin-live-reload/client");
}
@customElement("ak-interface-admin")
export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
//#region Properties
export class AdminInterface extends AuthenticatedInterface {
@property({ type: Boolean })
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
@property({ type: Boolean })
public notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
apiDrawerOpen = getURLParam("apiDrawerOpen", false);
@property({ type: Boolean })
public apiDrawerOpen = getURLParam("apiDrawerOpen", false);
ws: WebsocketClient;
protected readonly ws: WebsocketClient;
@property({
type: Object,
attribute: false,
reflect: false,
})
public user?: SessionUser;
@state()
user?: SessionUser;
@query("ak-about-modal")
public aboutModal?: AboutModal;
aboutModal?: AboutModal;
@property({ type: Boolean, reflect: true })
public sidebarOpen: boolean;
@eventOptions({ passive: true })
protected sidebarListener(event: CustomEvent<SidebarToggleEventDetail>) {
this.sidebarOpen = !!event.detail.open;
}
#sidebarMatcher: MediaQueryList;
#sidebarMediaQueryListener = (event: MediaQueryListEvent) => {
this.sidebarOpen = event.matches;
};
//#endregion
//#region Styles
static styles: CSSResult[] = [
PFBase,
PFPage,
PFButton,
PFDrawer,
PFNav,
css`
.pf-c-page__main,
.pf-c-drawer__content,
.pf-c-page__drawer {
z-index: auto !important;
background-color: transparent;
}
.display-none {
display: none;
}
.pf-c-page {
background-color: var(--pf-c-page--BackgroundColor) !important;
}
:host([theme="dark"]) {
/* Global page background colour */
static get styles(): CSSResult[] {
return [
PFBase,
PFPage,
PFButton,
PFDrawer,
css`
.pf-c-page__main,
.pf-c-drawer__content,
.pf-c-page__drawer {
z-index: auto !important;
background-color: transparent;
}
.display-none {
display: none;
}
.pf-c-page {
background-color: var(--pf-c-page--BackgroundColor) !important;
}
/* Global page background colour */
:host([theme="dark"]) .pf-c-page {
--pf-c-page--BackgroundColor: var(--ak-dark-background);
}
}
ak-page-navbar {
grid-area: header;
}
.ak-sidebar {
grid-area: nav;
}
.pf-c-drawer__panel {
z-index: var(--pf-global--ZIndex--xl);
}
`,
];
//#endregion
//#region Lifecycle
constructor() {
configureSentry(true);
super();
this.ws = new WebsocketClient();
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
this.sidebarOpen = this.#sidebarMatcher.matches;
ak-enterprise-status,
ak-version-banner {
grid-area: header;
}
ak-admin-sidebar {
grid-area: nav;
}
.pf-c-drawer__panel {
z-index: var(--pf-global--ZIndex--xl);
}
`,
];
}
public connectedCallback() {
super.connectedCallback();
constructor() {
super();
this.ws = new WebsocketClient();
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
this.notificationDrawerOpen = !this.notificationDrawerOpen;
@ -157,25 +108,16 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
apiDrawerOpen: this.apiDrawerOpen,
});
});
this.#sidebarMatcher.addEventListener("change", this.#sidebarMediaQueryListener, {
passive: true,
});
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this.#sidebarMatcher.removeEventListener("change", this.#sidebarMediaQueryListener);
}
async firstUpdated(): Promise<void> {
configureSentry(true);
this.user = await me();
const canAccessAdmin =
this.user.user.isSuperuser ||
// TODO: somehow add `access_admin_interface` to the API schema
this.user.user.systemPermissions.includes("access_admin_interface");
if (!canAccessAdmin && this.user.user.pk > 0) {
window.location.assign("/if/user/");
}
@ -183,14 +125,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
render(): TemplateResult {
const sidebarClasses = {
"pf-c-page__sidebar": true,
"pf-m-light": this.activeTheme === UiThemeEnum.Light,
"pf-m-expanded": this.sidebarOpen,
"pf-m-collapsed": !this.sidebarOpen,
};
const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;
const drawerClasses = {
"pf-m-expanded": drawerOpen,
"pf-m-collapsed": !drawerOpen,
@ -198,18 +136,11 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
return html` <ak-locale-context>
<div class="pf-c-page">
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
<ak-version-banner></ak-version-banner>
<ak-enterprise-status interface="admin"></ak-enterprise-status>
</ak-page-navbar>
<ak-sidebar class="${classMap(sidebarClasses)}">
${renderSidebarItems(AdminSidebarEntries)}
${this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed
? renderSidebarItems(AdminSidebarEnterpriseEntries)
: nothing}
</ak-sidebar>
<ak-enterprise-status interface="admin"></ak-enterprise-status>
<ak-version-banner></ak-version-banner>
<ak-admin-sidebar
class="pf-c-page__sidebar ${classMap(sidebarClasses)}"
></ak-admin-sidebar>
<div class="pf-c-page__drawer">
<div class="pf-c-drawer ${classMap(drawerClasses)}">
<div class="pf-c-drawer__main">

View File

@ -1,97 +1,186 @@
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
import { spread } from "@open-wc/lit-helpers";
import { msg } from "@lit/localize";
import { TemplateResult, html, nothing } from "lit";
import { repeat } from "lit/directives/repeat.js";
import { customElement, property, state } from "lit/decorators.js";
import { map } from "lit/directives/map.js";
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
// commonplace and singular enough to merit its own handler.
type SidebarEntry = [
path: string | null,
label: string,
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
children?: SidebarEntry[],
];
import { UiThemeEnum } from "@goauthentik/api";
import type { SessionUser, UserSelf } from "@goauthentik/api";
/**
* Recursively renders a sidebar entry.
*/
export function renderSidebarItem([
path,
label,
attributes,
children,
]: SidebarEntry): TemplateResult {
const properties = Array.isArray(attributes)
? { ".activeWhen": attributes }
: (attributes ?? {});
@customElement("ak-admin-sidebar")
export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement)) {
@property({ type: Boolean, reflect: true })
open = true;
if (path) {
properties.path = path;
@state()
impersonation: UserSelf["username"] | null = null;
constructor() {
super();
me().then((user: SessionUser) => {
this.impersonation = user.original ? user.user.username : null;
});
this.toggleOpen = this.toggleOpen.bind(this);
this.checkWidth = this.checkWidth.bind(this);
}
return html`<ak-sidebar-item ${spread(properties)}>
${label ? html`<span slot="label">${label}</span>` : nothing}
${children ? renderSidebarItems(children) : nothing}
</ak-sidebar-item>`;
// This has to be a bound method so the event listener can be removed on disconnection as
// needed.
toggleOpen() {
this.open = !this.open;
}
checkWidth() {
// This works just fine, but it assumes that the `--ak-sidebar--minimum-auto-width` is in
// REMs. If that changes, this code will have to be adjusted as well.
const minWidth =
parseFloat(getRootStyle("--ak-sidebar--minimum-auto-width")) *
parseFloat(getRootStyle("font-size"));
this.open = window.innerWidth >= minWidth;
}
connectedCallback() {
super.connectedCallback();
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
window.addEventListener("resize", this.checkWidth);
// After connecting to the DOM, we can now perform this check to see if the sidebar should
// be open by default.
this.checkWidth();
}
// The symmetry (☟, ☝) here is critical in that you want to start adding these handlers after
// connection, and removing them before disconnection.
disconnectedCallback() {
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
window.removeEventListener("resize", this.checkWidth);
super.disconnectedCallback();
}
render() {
return html`
<ak-sidebar
class="pf-c-page__sidebar ${this.open ? "pf-m-expanded" : "pf-m-collapsed"} ${this
.activeTheme === UiThemeEnum.Light
? "pf-m-light"
: ""}"
>
${this.renderSidebarItems()}
</ak-sidebar>
`;
}
updated() {
// This is permissible as`:host.classList` is not one of the properties Lit uses as a
// scheduling trigger. This sort of shenanigans can trigger an loop, in that it will trigger
// a browser reflow, which may trigger some other styling the application is monitoring,
// triggering a re-render which triggers a browser reflow, ad infinitum. But we've been
// living with that since jQuery, and it's both well-known and fortunately rare.
// eslint-disable-next-line wc/no-self-class
this.classList.remove("pf-m-expanded", "pf-m-collapsed");
// eslint-disable-next-line wc/no-self-class
this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed");
}
renderSidebarItems(): TemplateResult {
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
// commonplace and singular enough to merit its own handler.
type SidebarEntry = [
path: string | null,
label: string,
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
children?: SidebarEntry[],
];
// prettier-ignore
const sidebarContent: SidebarEntry[] = [
[null, msg("Dashboards"), { "?expanded": true }, [
["/administration/overview", msg("Overview")],
["/administration/dashboard/users", msg("User Statistics")],
["/administration/system-tasks", msg("System Tasks")]]],
[null, msg("Applications"), null, [
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
["/outpost/outposts", msg("Outposts")]]],
[null, msg("Events"), null, [
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
["/events/rules", msg("Notification Rules")],
["/events/transports", msg("Notification Transports")]]],
[null, msg("Customization"), null, [
["/policy/policies", msg("Policies")],
["/core/property-mappings", msg("Property Mappings")],
["/blueprints/instances", msg("Blueprints")],
["/policy/reputation", msg("Reputation scores")]]],
[null, msg("Flows and Stages"), null, [
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
["/flow/stages", msg("Stages")],
["/flow/stages/prompts", msg("Prompts")]]],
[null, msg("Directory"), null, [
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
["/core/tokens", msg("Tokens and App passwords")],
["/flow/stages/invitations", msg("Invitations")]]],
[null, msg("System"), null, [
["/core/brands", msg("Brands")],
["/crypto/certificates", msg("Certificates")],
["/outpost/integrations", msg("Outpost Integrations")],
["/admin/settings", msg("Settings")]]],
];
// Typescript requires the type here to correctly type the recursive path
type SidebarRenderer = (_: SidebarEntry) => TemplateResult;
const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => {
const properties = Array.isArray(attributes)
? { ".activeWhen": attributes }
: (attributes ?? {});
if (path) {
properties.path = path;
}
return html`<ak-sidebar-item ${spread(properties)}>
${label ? html`<span slot="label">${label}</span>` : nothing}
${map(children, renderOneSidebarItem)}
</ak-sidebar-item>`;
};
// prettier-ignore
return html`
${map(sidebarContent, renderOneSidebarItem)}
${this.renderEnterpriseMenu()}
`;
}
renderEnterpriseMenu() {
return this.can(CapabilitiesEnum.IsEnterprise)
? html`
<ak-sidebar-item>
<span slot="label">${msg("Enterprise")}</span>
<ak-sidebar-item path="/enterprise/licenses">
<span slot="label">${msg("Licenses")}</span>
</ak-sidebar-item>
</ak-sidebar-item>
`
: nothing;
}
}
/**
* Recursively renders a collection of sidebar entries.
*/
export function renderSidebarItems(entries: readonly SidebarEntry[]) {
return repeat(entries, ([path, label]) => path || label, renderSidebarItem);
declare global {
interface HTMLElementTagNameMap {
"ak-admin-sidebar": AkAdminSidebar;
}
}
// prettier-ignore
export const AdminSidebarEntries: readonly SidebarEntry[] = [
[null, msg("Dashboards"), { "?expanded": true }, [
["/administration/overview", msg("Overview")],
["/administration/dashboard/users", msg("User Statistics")],
["/administration/system-tasks", msg("System Tasks")]]
],
[null, msg("Applications"), null, [
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
["/outpost/outposts", msg("Outposts")]]
],
[null, msg("Events"), null, [
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
["/events/rules", msg("Notification Rules")],
["/events/transports", msg("Notification Transports")]]
],
[null, msg("Customization"), null, [
["/policy/policies", msg("Policies")],
["/core/property-mappings", msg("Property Mappings")],
["/blueprints/instances", msg("Blueprints")],
["/policy/reputation", msg("Reputation scores")]]
],
[null, msg("Flows and Stages"), null, [
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
["/flow/stages", msg("Stages")],
["/flow/stages/prompts", msg("Prompts")]]
],
[null, msg("Directory"), null, [
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
["/core/tokens", msg("Tokens and App passwords")],
["/flow/stages/invitations", msg("Invitations")]]
],
[null, msg("System"), null, [
["/core/brands", msg("Brands")],
["/crypto/certificates", msg("Certificates")],
["/outpost/integrations", msg("Outpost Integrations")],
["/admin/settings", msg("Settings")]]
],
];
// prettier-ignore
export const AdminSidebarEnterpriseEntries: readonly SidebarEntry[] = [
[null, msg("Enterprise"), null, [
["/enterprise/licenses", msg("Licenses"), null]
],
]]

View File

@ -0,0 +1,5 @@
import { AdminInterface } from "./AdminInterface";
import "./AdminInterface";
export { AdminInterface };
export default AdminInterface;

View File

@ -94,13 +94,10 @@ export class AdminOverviewPage extends AdminOverviewBase {
}
render(): TemplateResult {
const username = this.user?.user.name || this.user?.user.username;
const name = this.user?.user.name ?? this.user?.user.username;
return html` <ak-page-header
header=${msg(str`Welcome, ${username || ""}.`)}
description=${msg("General system status")}
?hasIcon=${false}
>
return html`<ak-page-header description=${msg("General system status")} ?hasIcon=${false}>
<span slot="header"> ${msg(str`Welcome, ${name || ""}.`)} </span>
</ak-page-header>
<section class="pf-c-page__main-section">
<div class="pf-l-grid pf-m-gutter">

View File

@ -1,4 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
@ -183,14 +184,20 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
label=${msg("Reputation: lower limit")}
required
name="reputationLowerLimit"
value="${this._settings?.reputationLowerLimit ?? DEFAULT_REPUTATION_LOWER_LIMIT}"
value="${first(
this._settings?.reputationLowerLimit,
DEFAULT_REPUTATION_LOWER_LIMIT,
)}"
help=${msg("Reputation cannot decrease lower than this value. Zero or negative.")}
></ak-number-input>
<ak-number-input
label=${msg("Reputation: upper limit")}
required
name="reputationUpperLimit"
value="${this._settings?.reputationUpperLimit ?? DEFAULT_REPUTATION_UPPER_LIMIT}"
value="${first(
this._settings?.reputationUpperLimit,
DEFAULT_REPUTATION_UPPER_LIMIT,
)}"
help=${msg("Reputation cannot increase higher than this value. Zero or positive.")}
></ak-number-input>
<ak-form-element-horizontal label=${msg("Footer links")} name="footerLinks">
@ -250,7 +257,7 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
label=${msg("Default token length")}
required
name="defaultTokenLength"
value="${this._settings?.defaultTokenLength ?? 60}"
value="${first(this._settings?.defaultTokenLength, 60)}"
help=${msg("Default length of generated tokens")}
></ak-number-input>
`;

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