Compare commits
2 Commits
core/fix-g
...
server-con
Author | SHA1 | Date | |
---|---|---|---|
29a3117a94 | |||
66e40720c5 |
3
.github/workflows/ci-main.yml
vendored
3
.github/workflows/ci-main.yml
vendored
@ -200,7 +200,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: web/dist
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', '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 }}
|
||||
|
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -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
|
||||
|
10
Dockerfile
10
Dockerfile
@ -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.3 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.2 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" \
|
||||
|
@ -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).
|
||||
|
@ -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>
|
||||
|
@ -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(
|
||||
|
@ -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(),
|
||||
}
|
||||
|
@ -16,12 +16,10 @@ from drf_spectacular.utils import (
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, IntegerField, SerializerMethodField
|
||||
from rest_framework.permissions import SAFE_METHODS, BasePermission
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ListSerializer, ValidationError
|
||||
from rest_framework.validators import UniqueValidator
|
||||
from rest_framework.views import View
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
@ -87,6 +85,34 @@ class GroupSerializer(ModelSerializer):
|
||||
raise ValidationError(_("Cannot set group as parent of itself."))
|
||||
return parent
|
||||
|
||||
def validate_is_superuser(self, superuser: bool):
|
||||
"""Ensure that the user creating this group has permissions to set the superuser flag"""
|
||||
request: Request = self.context.get("request", None)
|
||||
if not request:
|
||||
return superuser
|
||||
# If we're updating an instance, and the state hasn't changed, we don't need to check perms
|
||||
if self.instance and superuser == self.instance.is_superuser:
|
||||
return superuser
|
||||
user: User = request.user
|
||||
perm = (
|
||||
"authentik_core.enable_group_superuser"
|
||||
if superuser
|
||||
else "authentik_core.disable_group_superuser"
|
||||
)
|
||||
has_perm = user.has_perm(perm)
|
||||
if self.instance and not has_perm:
|
||||
has_perm = user.has_perm(perm, self.instance)
|
||||
if not has_perm:
|
||||
raise ValidationError(
|
||||
_(
|
||||
(
|
||||
"User does not have permission to set "
|
||||
"superuser status to {superuser_status}."
|
||||
).format_map({"superuser_status": superuser})
|
||||
)
|
||||
)
|
||||
return superuser
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = [
|
||||
@ -154,36 +180,6 @@ class GroupFilter(FilterSet):
|
||||
fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"]
|
||||
|
||||
|
||||
class SuperuserSetter(BasePermission):
|
||||
"""Check for enable_group_superuser or disable_group_superuser permissions"""
|
||||
|
||||
message = _("User does not have permission to set the given superuser status.")
|
||||
enable_perm = "authentik_core.enable_group_superuser"
|
||||
disable_perm = "authentik_core.disable_group_superuser"
|
||||
|
||||
def has_permission(self, request: Request, view: View):
|
||||
if request.method != "POST":
|
||||
return True
|
||||
|
||||
is_superuser = request.data.get("is_superuser", False)
|
||||
if not is_superuser:
|
||||
return True
|
||||
|
||||
return request.user.has_perm(self.enable_perm)
|
||||
|
||||
def has_object_permission(self, request: Request, view: View, object: Group):
|
||||
if request.method in SAFE_METHODS:
|
||||
return True
|
||||
|
||||
new_value = request.data.get("is_superuser")
|
||||
old_value = object.is_superuser
|
||||
if new_value is None or new_value == old_value:
|
||||
return True
|
||||
|
||||
perm = self.enable_perm if new_value else self.disable_perm
|
||||
return request.user.has_perm(perm) or request.user.has_perm(perm, object)
|
||||
|
||||
|
||||
class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
"""Group Viewset"""
|
||||
|
||||
@ -196,7 +192,6 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
serializer_class = GroupSerializer
|
||||
search_fields = ["name", "is_superuser"]
|
||||
filterset_class = GroupFilter
|
||||
permission_classes = [SuperuserSetter]
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -2,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)
|
||||
|
@ -31,10 +31,7 @@ class PickleSerializer:
|
||||
|
||||
def loads(self, data):
|
||||
"""Unpickle data to be loaded from redis"""
|
||||
try:
|
||||
return pickle.loads(data) # nosec
|
||||
except Exception:
|
||||
return {}
|
||||
return pickle.loads(data) # nosec
|
||||
|
||||
|
||||
def _migrate_session(
|
||||
|
@ -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,
|
||||
),
|
||||
]
|
@ -1,5 +1,14 @@
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
{{ config_json|json_script:":ak-config:" }}
|
||||
|
||||
{{ brand_json|json_script:":ak-brand:" }}
|
||||
|
||||
<meta name="ak-version-family" content="{{ version_family }}">
|
||||
<meta name="ak-version-subdomain" content="{{ version_subdomain }}">
|
||||
<meta name="ak-build" content="{{ build }}">
|
||||
<meta name="ak-base-url" content="{{ base_url }}">
|
||||
<meta name="ak-base-url-rel" content="{{ base_url_rel }}">
|
||||
|
||||
<script>
|
||||
window.authentik = {
|
||||
|
@ -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 %}
|
||||
|
@ -118,25 +118,12 @@ class TestGroupsAPI(APITestCase):
|
||||
reverse("authentik_api:group-list"),
|
||||
data={"name": generate_id(), "is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 403)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
res.content,
|
||||
{"detail": "User does not have permission to set the given superuser status."},
|
||||
{"is_superuser": ["User does not have permission to set superuser status to True."]},
|
||||
)
|
||||
|
||||
def test_superuser_update_object_perm(self):
|
||||
"""Test updating a superuser group with object permission"""
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=False)
|
||||
assign_perm("view_group", self.login_user, group)
|
||||
assign_perm("change_group", self.login_user, group)
|
||||
assign_perm("enable_group_superuser", self.login_user, group)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_superuser_update_no_perm(self):
|
||||
"""Test updating a superuser group without permission"""
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
||||
@ -147,10 +134,10 @@ class TestGroupsAPI(APITestCase):
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"is_superuser": False},
|
||||
)
|
||||
self.assertEqual(res.status_code, 403)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
res.content,
|
||||
{"detail": "User does not have permission to set the given superuser status."},
|
||||
{"is_superuser": ["User does not have permission to set superuser status to False."]},
|
||||
)
|
||||
|
||||
def test_superuser_update_no_change(self):
|
||||
@ -176,27 +163,3 @@ class TestGroupsAPI(APITestCase):
|
||||
data={"name": generate_id(), "is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
||||
def test_superuser_create_no_perm(self):
|
||||
"""Test creating a superuser group with no permission"""
|
||||
assign_perm("authentik_core.add_group", self.login_user)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:group-list"),
|
||||
data={"name": generate_id(), "is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 403)
|
||||
self.assertJSONEqual(
|
||||
res.content,
|
||||
{"detail": "User does not have permission to set the given superuser status."},
|
||||
)
|
||||
|
||||
def test_no_superuser_create_no_perm(self):
|
||||
"""Test creating a non-superuser group with no permission"""
|
||||
assign_perm("authentik_core.add_group", self.login_user)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:group-list"),
|
||||
data={"name": generate_id()},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Interface views"""
|
||||
|
||||
from json import dumps
|
||||
from typing import Any
|
||||
|
||||
from django.http import HttpRequest
|
||||
@ -46,14 +45,19 @@ class InterfaceView(TemplateView):
|
||||
"""Base interface view"""
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
kwargs["config_json"] = dumps(ConfigView(request=Request(self.request)).get_config().data)
|
||||
kwargs["brand_json"] = dumps(CurrentBrandSerializer(self.request.brand).data)
|
||||
"""Add common context data to all interface views"""
|
||||
|
||||
kwargs["config_json"] = ConfigView(request=Request(self.request)).get_config().data
|
||||
kwargs["brand_json"] = CurrentBrandSerializer(self.request.brand).data
|
||||
|
||||
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"
|
||||
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
|
||||
|
||||
kwargs["build"] = get_build_hash()
|
||||
kwargs["url_kwargs"] = self.kwargs
|
||||
kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/"))
|
||||
kwargs["base_url_rel"] = CONFIG.get("web.path", "/")
|
||||
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
{% block head_before %}
|
||||
{{ block.super }}
|
||||
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||
<link rel="prefetch" href="{{ flow.background_url }}" />
|
||||
{% if flow.compatibility_mode and not inspector %}
|
||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
||||
{% endif %}
|
||||
@ -21,7 +21,7 @@ window.authentik.flow = {
|
||||
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
||||
<style>
|
||||
:root {
|
||||
--ak-flow-background: url("{{ flow_background_url }}");
|
||||
--ak-flow-background: url("{{ flow.background_url }}");
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
@ -13,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)
|
||||
|
||||
|
@ -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": {
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -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",
|
||||
|
8
go.mod
8
go.mod
@ -19,7 +19,7 @@ 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
|
||||
@ -29,8 +29,8 @@ require (
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2025040.1
|
||||
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
|
||||
)
|
||||
|
20
go.sum
20
go.sum
@ -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=
|
||||
@ -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=
|
||||
|
@ -56,7 +56,6 @@ EXPOSE 3389 6636 9300
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
ENV GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/ldap"]
|
||||
|
@ -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
|
||||
|
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1014.0",
|
||||
"aws-cdk": "^2.1013.0",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1014.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1014.0.tgz",
|
||||
"integrity": "sha512-es101rtRAClix9BncNL54iW90MiOyRv4iCC5tv/firGDnidS6pPinuK0IIFt0RO6w0+3heRxWBXg8HY+f9877w==",
|
||||
"version": "2.1013.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1013.0.tgz",
|
||||
"integrity": "sha512-cbq4cOoEIZueMWenGgfI4RujS+AQ9GaMCTlW/3CnvEIhMD8j/tgZx7PTtgMuvwYrRoEeb/wTxgLPgUd5FhsoHA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1014.0",
|
||||
"aws-cdk": "^2.1013.0",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
4
packages/docusaurus-config/package-lock.json
generated
4
packages/docusaurus-config/package-lock.json
generated
@ -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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/docusaurus-config",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"description": "authentik's Docusaurus config",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
@ -76,7 +76,6 @@ EXPOSE 9000 9300 9443
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
ENV GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/proxy"]
|
||||
|
198
pyproject.toml
198
pyproject.toml
@ -3,114 +3,102 @@ name = "authentik"
|
||||
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.1",
|
||||
"django-prometheus==2.3.1",
|
||||
"django-redis==5.4.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-tenants==3.7.0",
|
||||
"djangorestframework==3.16.0",
|
||||
"djangorestframework-guardian==0.3.0",
|
||||
"docker==7.1.0",
|
||||
"drf-orjson-renderer==1.7.3",
|
||||
"drf-spectacular==0.28.0",
|
||||
"dumb-init==1.2.5.post1",
|
||||
"duo-client==5.5.0",
|
||||
"fido2==1.2.0",
|
||||
"flower==2.0.1",
|
||||
"geoip2==5.1.0",
|
||||
"geopy==2.4.1",
|
||||
"google-api-python-client==2.169.0",
|
||||
"gssapi==1.9.0",
|
||||
"gunicorn==23.0.0",
|
||||
"jsonpatch==1.33",
|
||||
"jwcrypto==1.5.6",
|
||||
"kubernetes==32.0.1",
|
||||
"ldap3==2.9.1",
|
||||
"lxml==5.4.0",
|
||||
"msgraph-sdk==1.30.0",
|
||||
"opencontainers==0.0.14",
|
||||
"packaging==25.0",
|
||||
"paramiko==3.5.1",
|
||||
"psycopg[c,pool]==3.2.9",
|
||||
"pydantic==2.11.4",
|
||||
"pydantic-scim==0.0.8",
|
||||
"pyjwt==2.10.1",
|
||||
"pyrad==2.4",
|
||||
"python-kadmin-rs==0.6.0",
|
||||
"pyyaml==6.0.2",
|
||||
"requests-oauthlib==2.0.0",
|
||||
"scim2-filter-parser==0.7.0",
|
||||
"sentry-sdk==2.28.0",
|
||||
"service-identity==24.2.0",
|
||||
"setproctitle==1.3.6",
|
||||
"structlog==25.3.0",
|
||||
"swagger-spec-validator==3.0.4",
|
||||
"tenant-schemas-celery==4.0.1",
|
||||
"twilio==9.6.1",
|
||||
"ua-parser==1.0.1",
|
||||
"unidecode==1.4.0",
|
||||
"urllib3<3",
|
||||
"uvicorn[standard]==0.34.2",
|
||||
"watchdog==6.0.0",
|
||||
"webauthn==2.5.2",
|
||||
"wsproto==1.2.0",
|
||||
"xmlsec==1.3.15",
|
||||
"zxcvbn==4.5.0",
|
||||
"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]
|
||||
|
@ -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"]
|
||||
|
@ -56,7 +56,6 @@ EXPOSE 1812/udp 9300
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
ENV GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/radius"]
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -26,7 +26,6 @@ from selenium import webdriver
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.remote.command import Command
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
@ -198,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,7 +235,7 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
return element
|
||||
|
||||
def login(self):
|
||||
"""Do entire login flow"""
|
||||
"""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)
|
||||
|
||||
|
10
web/package-lock.json
generated
10
web/package-lock.json
generated
@ -7,6 +7,7 @@
|
||||
"": {
|
||||
"name": "@goauthentik/web",
|
||||
"version": "0.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
".",
|
||||
@ -9471,9 +9472,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001716",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz",
|
||||
"integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==",
|
||||
"version": "1.0.30001667",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
|
||||
"integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -9488,8 +9489,7 @@
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
]
|
||||
},
|
||||
"node_modules/ccount": {
|
||||
"version": "2.0.1",
|
||||
|
@ -19,6 +19,7 @@
|
||||
"lint:precommit": "wireit",
|
||||
"lint:types": "wireit",
|
||||
"lit-analyse": "wireit",
|
||||
"postinstall": "bash scripts/patch-spotlight.sh",
|
||||
"precommit": "wireit",
|
||||
"prettier": "wireit",
|
||||
"prettier-check": "wireit",
|
||||
|
@ -6,7 +6,7 @@
|
||||
* @import { Message as ESBuildMessage } from "esbuild";
|
||||
*/
|
||||
|
||||
const logPrefix = "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);
|
||||
}
|
||||
|
||||
|
@ -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")}
|
||||
|
33
web/scripts/patch-spotlight.sh
Normal file
33
web/scripts/patch-spotlight.sh
Normal 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
|
@ -1,6 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
@ -33,7 +33,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||
const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
||||
let build: string | TemplateResult = msg("Release");
|
||||
if (globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
if (ServerContext.config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
build = msg("Development");
|
||||
} else if (version.buildHash !== "") {
|
||||
build = html`<a
|
||||
@ -58,10 +58,12 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
}
|
||||
|
||||
renderModal() {
|
||||
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
let product = ServerContext.brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
|
||||
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
|
||||
product += ` ${msg("Enterprise")}`;
|
||||
}
|
||||
|
||||
return html`<div
|
||||
class="pf-c-backdrop"
|
||||
@click=${(e: PointerEvent) => {
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
EVENT_SIDEBAR_TOGGLE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { setSentryPII, tryInitializeSentry } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
||||
@ -131,9 +132,9 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
//#region Lifecycle
|
||||
|
||||
constructor() {
|
||||
configureSentry(true);
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
|
||||
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||
}
|
||||
@ -167,14 +168,21 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
tryInitializeSentry(ServerContext.config);
|
||||
this.user = await me();
|
||||
|
||||
setSentryPII(this.user.user);
|
||||
|
||||
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) {
|
||||
console.debug(
|
||||
"authentik/admin: User does not have access to admin interface. Redirecting...",
|
||||
);
|
||||
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,8 @@ export class ApplicationViewPage extends AKElement {
|
||||
|
||||
renderApp(): TemplateResult {
|
||||
if (!this.application) {
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
return html`<ak-tabs>
|
||||
${this.missingOutpost
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/components/ak-toggle-group";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/TokenCopyButton";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import { groupBy } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BasePolicyForm } from "@goauthentik/admin/policies/BasePolicyForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
|
@ -42,7 +42,7 @@ export class ProviderViewPage extends AKElement {
|
||||
|
||||
renderProvider(): TemplateResult {
|
||||
if (!this.provider) {
|
||||
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
||||
}
|
||||
switch (this.provider?.component) {
|
||||
case "ak-provider-saml-form":
|
||||
|
@ -432,7 +432,7 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
<div class="pf-c-card__body">
|
||||
${this.preview
|
||||
? html`<pre>${JSON.stringify(this.preview?.preview, null, 4)}</pre>`
|
||||
: html` <ak-empty-state loading></ak-empty-state> `}
|
||||
: html` <ak-empty-state ?loading=${true}></ak-empty-state> `}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
@ -502,7 +502,7 @@ export class SAMLProviderViewPage extends AKElement {
|
||||
|
||||
renderTabPreview(): TemplateResult {
|
||||
if (!this.preview) {
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
}
|
||||
return html` <div
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
|
||||
|
@ -34,7 +34,7 @@ export class SourceViewPage extends AKElement {
|
||||
|
||||
renderSource(): TemplateResult {
|
||||
if (!this.source) {
|
||||
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
||||
}
|
||||
switch (this.source?.component) {
|
||||
case "ak-source-kerberos-form":
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
GroupMatchingModeToLabel,
|
||||
UserMatchingModeToLabel,
|
||||
} from "@goauthentik/admin/sources/oauth/utils";
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context.js";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
@ -60,8 +61,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
kerberosSourceRequest: data as unknown as KerberosSourceRequest,
|
||||
});
|
||||
}
|
||||
const c = await config();
|
||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const { capabilities } = ServerContext.config;
|
||||
|
||||
if (capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const icon = this.getFormFiles()["icon"];
|
||||
if (icon || this.clearIcon) {
|
||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
GroupMatchingModeToLabel,
|
||||
UserMatchingModeToLabel,
|
||||
} from "@goauthentik/admin/sources/oauth/utils";
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context.js";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
@ -85,8 +86,9 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
oAuthSourceRequest: data as unknown as OAuthSourceRequest,
|
||||
});
|
||||
}
|
||||
const c = await config();
|
||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const { capabilities } = ServerContext.config;
|
||||
|
||||
if (capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const icon = this.getFormFiles()["icon"];
|
||||
if (icon || this.clearIcon) {
|
||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
GroupMatchingModeToLabel,
|
||||
UserMatchingModeToLabel,
|
||||
} from "@goauthentik/admin/sources/oauth/utils";
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context.js";
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
WithCapabilitiesConfig,
|
||||
@ -61,8 +62,9 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
||||
sAMLSourceRequest: data,
|
||||
});
|
||||
}
|
||||
const c = await config();
|
||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const { capabilities } = ServerContext.config;
|
||||
|
||||
if (capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const icon = this.getFormFiles()["icon"];
|
||||
if (icon || this.clearIcon) {
|
||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import { Form } from "@goauthentik/elements/forms/Form";
|
||||
|
||||
@ -21,7 +21,7 @@ export class UserImpersonateForm extends Form<ImpersonationRequest> {
|
||||
impersonationRequest: data,
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = globalAK().api.base;
|
||||
window.location.href = ServerContext.baseURL;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,23 +5,14 @@ import {
|
||||
LoggingMiddleware,
|
||||
} from "@goauthentik/common/api/middleware";
|
||||
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { SentryMiddleware } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
|
||||
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
||||
import { Configuration, CurrentBrand } from "@goauthentik/api";
|
||||
|
||||
// HACK: Workaround for ESBuild not being able to hoist import statement across entrypoints.
|
||||
// This can be removed after ESBuild uses a single build context for all entrypoints.
|
||||
export { CSRFHeaderName };
|
||||
|
||||
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
|
||||
export function config(): Promise<Config> {
|
||||
if (!globalConfigPromise) {
|
||||
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve();
|
||||
}
|
||||
return globalConfigPromise;
|
||||
}
|
||||
|
||||
export function brandSetFavicon(brand: CurrentBrand) {
|
||||
/**
|
||||
* <link rel="icon" href="/static/dist/assets/icons/icon.png">
|
||||
@ -53,27 +44,22 @@ export function brandSetLocale(brand: CurrentBrand) {
|
||||
);
|
||||
}
|
||||
|
||||
let globalBrandPromise: Promise<CurrentBrand> | undefined = Promise.resolve(globalAK().brand);
|
||||
export function brand(): Promise<CurrentBrand> {
|
||||
if (!globalBrandPromise) {
|
||||
globalBrandPromise = new CoreApi(DEFAULT_CONFIG)
|
||||
.coreBrandsCurrentRetrieve()
|
||||
.then((brand) => {
|
||||
brandSetFavicon(brand);
|
||||
brandSetLocale(brand);
|
||||
return brand;
|
||||
});
|
||||
}
|
||||
return globalBrandPromise;
|
||||
export function getMetaContent(key: string): string {
|
||||
const metaEl = document.querySelector<HTMLMetaElement>(`meta[name=${key}]`);
|
||||
if (!metaEl) return "";
|
||||
|
||||
return metaEl.content;
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG = new Configuration({
|
||||
basePath: `${globalAK().api.base}api/v3`,
|
||||
basePath: `${ServerContext.baseURL}api/v3`,
|
||||
headers: {
|
||||
"sentry-trace": ServerContext.sentryTrace,
|
||||
},
|
||||
middleware: [
|
||||
new CSRFMiddleware(),
|
||||
new EventMiddleware(),
|
||||
new LoggingMiddleware(globalAK().brand),
|
||||
new SentryMiddleware(),
|
||||
new LoggingMiddleware(ServerContext.brand),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api";
|
||||
|
||||
export interface GlobalAuthentik {
|
||||
_converted?: boolean;
|
||||
locale?: string;
|
||||
flow?: {
|
||||
layout: string;
|
||||
};
|
||||
config: Config;
|
||||
brand: CurrentBrand;
|
||||
versionFamily: string;
|
||||
versionSubdomain: string;
|
||||
build: string;
|
||||
api: {
|
||||
base: string;
|
||||
relBase: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuthentikWindow {
|
||||
authentik: GlobalAuthentik;
|
||||
}
|
||||
|
||||
export function globalAK(): GlobalAuthentik {
|
||||
const ak = (window as unknown as AuthentikWindow).authentik;
|
||||
if (ak && !ak._converted) {
|
||||
ak._converted = true;
|
||||
ak.brand = CurrentBrandFromJSON(ak.brand);
|
||||
ak.config = ConfigFromJSON(ak.config);
|
||||
}
|
||||
const apiBase = new URL(process.env.AK_API_BASE_PATH || window.location.origin);
|
||||
if (!ak) {
|
||||
return {
|
||||
config: ConfigFromJSON({
|
||||
capabilities: [],
|
||||
}),
|
||||
brand: CurrentBrandFromJSON({
|
||||
ui_footer_links: [],
|
||||
}),
|
||||
versionFamily: "",
|
||||
versionSubdomain: "",
|
||||
build: "",
|
||||
api: {
|
||||
base: apiBase.toString(),
|
||||
relBase: apiBase.pathname,
|
||||
},
|
||||
};
|
||||
}
|
||||
return ak;
|
||||
}
|
||||
|
||||
export function docLink(path: string): string {
|
||||
const ak = globalAK();
|
||||
// Default case or beta build which should always point to latest
|
||||
if (!ak || ak.build !== "") {
|
||||
return `https://goauthentik.io${path}`;
|
||||
}
|
||||
return `https://${ak.versionSubdomain}.goauthentik.io${path}`;
|
||||
}
|
@ -1,122 +1,131 @@
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||
import {
|
||||
ErrorEvent,
|
||||
EventHint,
|
||||
browserTracingIntegration,
|
||||
init,
|
||||
setTag,
|
||||
setUser,
|
||||
} from "@sentry/browser";
|
||||
import { getTraceData } from "@sentry/core";
|
||||
import * as Spotlight from "@spotlightjs/spotlight";
|
||||
import { RouteInterfaceName, readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||
import { BrowserOptions, browserTracingIntegration, init, setTag, setUser } from "@sentry/browser";
|
||||
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
FetchParams,
|
||||
Middleware,
|
||||
RequestContext,
|
||||
ResponseError,
|
||||
} from "@goauthentik/api";
|
||||
import { CapabilitiesEnum, Config, ResponseError, UserSelf } from "@goauthentik/api";
|
||||
|
||||
/**
|
||||
* A generic error that can be thrown without triggering Sentry's reporting.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export class SentryIgnoredError extends Error {}
|
||||
|
||||
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
||||
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
||||
/**
|
||||
* Attempt initializing Spotlight.
|
||||
*
|
||||
* @see {@link https://spotlightjs.com/ Spotlight}
|
||||
* @category Sentry
|
||||
*/
|
||||
export async function tryInitializingSpotlight() {
|
||||
return import("@spotlightjs/spotlight").then((Spotlight) =>
|
||||
Spotlight.init({ injectImmediately: true }),
|
||||
);
|
||||
}
|
||||
|
||||
let _sentryConfigured = false;
|
||||
|
||||
export function configureSentry(canDoPpi = false) {
|
||||
const cfg = globalAK().config;
|
||||
const debug = cfg.capabilities.includes(CapabilitiesEnum.CanDebug);
|
||||
if (!cfg.errorReporting.enabled && !debug) {
|
||||
return cfg;
|
||||
}
|
||||
init({
|
||||
dsn: cfg.errorReporting.sentryDsn,
|
||||
ignoreErrors: [
|
||||
/network/gi,
|
||||
/fetch/gi,
|
||||
/module/gi,
|
||||
// Error on edge on ios,
|
||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||
// Seems to be an issue in Safari and Firefox
|
||||
/MutationObserver.observe/gi,
|
||||
/NS_ERROR_FAILURE/gi,
|
||||
],
|
||||
release: `authentik@${VERSION}`,
|
||||
integrations: [
|
||||
browserTracingIntegration({
|
||||
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
|
||||
instrumentNavigation: false,
|
||||
instrumentPageLoad: false,
|
||||
traceFetch: false,
|
||||
}),
|
||||
],
|
||||
tracePropagationTargets: [window.location.origin],
|
||||
tracesSampleRate: debug ? 1.0 : cfg.errorReporting.tracesSampleRate,
|
||||
environment: cfg.errorReporting.environment,
|
||||
beforeSend: (
|
||||
event: ErrorEvent,
|
||||
hint: EventHint,
|
||||
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
|
||||
if (!hint) {
|
||||
return event;
|
||||
}
|
||||
if (hint.originalException instanceof SentryIgnoredError) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
hint.originalException instanceof ResponseError ||
|
||||
hint.originalException instanceof DOMException
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Default Sentry options for the browser.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
const DEFAULT_SENTRY_BROWSER_OPTIONS = {
|
||||
ignoreErrors: [
|
||||
/network/gi,
|
||||
/fetch/gi,
|
||||
/module/gi,
|
||||
// Error on edge on ios,
|
||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||
// Seems to be an issue in Safari and Firefox
|
||||
/MutationObserver.observe/gi,
|
||||
/NS_ERROR_FAILURE/gi,
|
||||
],
|
||||
release: `authentik@${VERSION}`,
|
||||
integrations: [
|
||||
browserTracingIntegration({
|
||||
shouldCreateSpanForRequest: (url: string) => {
|
||||
return url.startsWith(window.location.host);
|
||||
},
|
||||
}),
|
||||
],
|
||||
beforeSend: (event, hint) => {
|
||||
if (!hint) {
|
||||
return event;
|
||||
},
|
||||
});
|
||||
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
||||
if (window.location.pathname.includes("if/")) {
|
||||
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
||||
}
|
||||
if (debug) {
|
||||
Spotlight.init({
|
||||
injectImmediately: true,
|
||||
integrations: [
|
||||
Spotlight.sentry({
|
||||
injectIntoSDK: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
console.debug("authentik/config: Enabled Sentry Spotlight");
|
||||
}
|
||||
if (cfg.errorReporting.sendPii && canDoPpi) {
|
||||
me().then((user) => {
|
||||
setUser({ email: user.user.email });
|
||||
console.debug("authentik/config: Sentry with PII enabled.");
|
||||
});
|
||||
} else {
|
||||
console.debug("authentik/config: Sentry enabled.");
|
||||
}
|
||||
_sentryConfigured = true;
|
||||
}
|
||||
if (hint.originalException instanceof SentryIgnoredError) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
hint.originalException instanceof ResponseError ||
|
||||
hint.originalException instanceof DOMException
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
} as const satisfies BrowserOptions;
|
||||
|
||||
/**
|
||||
* Include the given user in Sentry events.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export function setSentryPII(user: UserSelf): void {
|
||||
console.debug("authentik/sentry: PII enabled.");
|
||||
|
||||
setUser({ email: user.email });
|
||||
}
|
||||
|
||||
export class SentryMiddleware implements Middleware {
|
||||
pre?(context: RequestContext): Promise<FetchParams | void> {
|
||||
if (!_sentryConfigured) {
|
||||
return Promise.resolve(context);
|
||||
}
|
||||
const traceData = getTraceData();
|
||||
// @ts-ignore
|
||||
context.init.headers["baggage"] = traceData["baggage"];
|
||||
// @ts-ignore
|
||||
context.init.headers["sentry-trace"] = traceData["sentry-trace"];
|
||||
return Promise.resolve(context);
|
||||
/**
|
||||
* Include the given capabilities in Sentry events.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export function setSentryCapabilities(capabilities: CapabilitiesEnum[]): void {
|
||||
setTag("authentik.capabilities", capabilities.join(","));
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the given route interface in Sentry events.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export function setSentryInterface(interfaceName: RouteInterfaceName) {
|
||||
setTag("authentik.component", `web/${interfaceName}}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to initialize Sentry with the given configuration.
|
||||
*
|
||||
* @see {@linkcode setSentryPII}
|
||||
* @see {@linkcode setSentryCapabilities}
|
||||
* @see {@linkcode setSentryInterface}
|
||||
* @category Sentry
|
||||
*/
|
||||
export function tryInitializeSentry({ errorReporting, capabilities }: Config): void {
|
||||
if (!errorReporting.enabled) return;
|
||||
|
||||
init({
|
||||
...DEFAULT_SENTRY_BROWSER_OPTIONS,
|
||||
dsn: errorReporting.sentryDsn,
|
||||
tracesSampleRate: errorReporting.tracesSampleRate,
|
||||
environment: errorReporting.environment,
|
||||
enabled: process.env.NODE_ENV !== "development",
|
||||
});
|
||||
|
||||
setSentryCapabilities(capabilities);
|
||||
setSentryInterface(readInterfaceRouteParam());
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV === "development" &&
|
||||
capabilities.includes(CapabilitiesEnum.CanDebug)
|
||||
) {
|
||||
tryInitializingSpotlight()
|
||||
.then(() => {
|
||||
console.debug("authentik/sentry: Sentry with Spotlight enabled.");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn("authentik/sentry: Failed to load Spotlight", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
116
web/src/common/server-context.ts
Normal file
116
web/src/common/server-context.ts
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @file Server context singleton.
|
||||
*/
|
||||
import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api";
|
||||
|
||||
function readMetaElement(key: string, fallback: string = ""): string {
|
||||
const metaElement = document.querySelector<HTMLMetaElement>(`meta[name="${key}"]`);
|
||||
const value = metaElement?.getAttribute("content") || fallback;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
interface ServerContextValue {
|
||||
/**
|
||||
* Server-injected authentik configuration.
|
||||
*/
|
||||
config: Readonly<Config>;
|
||||
|
||||
/**
|
||||
* Brand information used to customize the UI.
|
||||
*/
|
||||
brand: Readonly<CurrentBrand>;
|
||||
|
||||
/**
|
||||
* A semantic versioning string representing the current version of authentik.
|
||||
*/
|
||||
versionFamily: string;
|
||||
|
||||
/**
|
||||
* A subdomain-compatible version string representing the current version of authentik.
|
||||
*/
|
||||
versionSubdomain: string;
|
||||
|
||||
/**
|
||||
* A build hash string representing the current build of authentik.
|
||||
*/
|
||||
build: string;
|
||||
|
||||
/**
|
||||
* The base URL of the authentik instance.
|
||||
*/
|
||||
baseURL: string;
|
||||
|
||||
/**
|
||||
* The relative base URL of the authentik instance.
|
||||
*/
|
||||
baseURLRelative: string;
|
||||
|
||||
/**
|
||||
* The layout of the flow, if any.
|
||||
*/
|
||||
flowLayout: string;
|
||||
|
||||
/**
|
||||
* The Sentry trace ID for the current request.
|
||||
*/
|
||||
sentryTrace: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the server context from the DOM.
|
||||
*/
|
||||
export function refreshServerContext(): Readonly<ServerContextValue> {
|
||||
const configElement = document.getElementById(":ak-config:");
|
||||
|
||||
const config = configElement?.textContent
|
||||
? ConfigFromJSON(JSON.parse(configElement.textContent))
|
||||
: ConfigFromJSON({
|
||||
capabilities: [],
|
||||
});
|
||||
|
||||
const brandElement = document.getElementById(":ak-brand:");
|
||||
|
||||
const brand = brandElement?.textContent
|
||||
? CurrentBrandFromJSON(JSON.parse(brandElement.textContent))
|
||||
: CurrentBrandFromJSON({
|
||||
ui_footer_links: [],
|
||||
});
|
||||
|
||||
const apiBaseURL = new URL(process.env.AK_API_BASE_PATH || window.location.origin);
|
||||
|
||||
const value: ServerContextValue = {
|
||||
sentryTrace: readMetaElement("sentry-trace"),
|
||||
baseURL: readMetaElement("ak-base-url") || apiBaseURL.toString(),
|
||||
baseURLRelative: readMetaElement("ak-base-url-rel"),
|
||||
|
||||
versionFamily: readMetaElement("ak-version-family"),
|
||||
versionSubdomain: readMetaElement("ak-version-subdomain"),
|
||||
|
||||
build: readMetaElement("ak-build"),
|
||||
|
||||
flowLayout: readMetaElement("ak-flow-layout"),
|
||||
config,
|
||||
brand,
|
||||
};
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server injected values used to configure application.
|
||||
*
|
||||
* @singleton
|
||||
*/
|
||||
export const ServerContext = refreshServerContext();
|
||||
|
||||
export function docLink(path: string): string {
|
||||
const { build, versionSubdomain } = ServerContext;
|
||||
|
||||
// Default case or beta build which should always point to latest
|
||||
if (build) {
|
||||
return new URL(path, "https://goauthentik.io").toString();
|
||||
}
|
||||
|
||||
return new URL(path, `https://${versionSubdomain}.goauthentik.io`).toString();
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { EVENT_MESSAGE, EVENT_WS_MESSAGE } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
|
||||
@ -22,11 +22,14 @@ export class WebsocketClient {
|
||||
|
||||
connect(): void {
|
||||
if (navigator.webdriver) return;
|
||||
const apiURL = new URL(globalAK().api.base);
|
||||
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${apiURL.host}${apiURL.pathname}ws/client/`;
|
||||
this.messageSocket = new WebSocket(wsUrl);
|
||||
|
||||
const apiURL = new URL(ServerContext.baseURL);
|
||||
|
||||
const wsURL = `${window.location.protocol.replace("http", "ws")}//${apiURL.host}${apiURL.pathname}ws/client/`;
|
||||
|
||||
this.messageSocket = new WebSocket(wsURL);
|
||||
this.messageSocket.addEventListener("open", () => {
|
||||
console.debug(`authentik/ws: connected to ${wsUrl}`);
|
||||
console.debug(`authentik/ws: connected to ${wsURL}`);
|
||||
this.retryDelay = 200;
|
||||
});
|
||||
this.messageSocket.addEventListener("close", (e) => {
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
EVENT_API_DRAWER_TOGGLE,
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
@ -146,7 +146,7 @@ export class NavigationButtons extends AKElement {
|
||||
<a
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
href="${globalAK().api.base}if/user/#/settings"
|
||||
href="${ServerContext.baseURL}if/user/#/settings"
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Settings")}>
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
@ -200,7 +200,7 @@ export class NavigationButtons extends AKElement {
|
||||
${this.renderSettings()}
|
||||
<div class="pf-c-page__header-tools-item">
|
||||
<a
|
||||
href="${globalAK().api.base}flows/-/default/invalidation/"
|
||||
href="${ServerContext.baseURL}flows/-/default/invalidation/"
|
||||
class="pf-c-button pf-m-plain"
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Sign out")}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import {
|
||||
StyleSheetInit,
|
||||
StyleSheetParent,
|
||||
@ -82,7 +82,7 @@ export class AKElement extends LitElement implements ThemedElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const { brand } = globalAK();
|
||||
const { brand } = ServerContext;
|
||||
|
||||
this.#preferredColorScheme = formatColorScheme(brand.uiTheme);
|
||||
this.activeTheme = resolveUITheme(brand?.uiTheme);
|
||||
|
@ -83,7 +83,7 @@ export class Diagram extends AKElement {
|
||||
}
|
||||
});
|
||||
if (!this.diagram) {
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
}
|
||||
return html`${until(
|
||||
mermaid.render("graph", this.diagram).then((r) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
@ -23,8 +23,8 @@ export class ConfigContextController implements ReactiveController {
|
||||
initialValue: undefined,
|
||||
});
|
||||
// Pre-hydrate from template-embedded config
|
||||
this.context.setValue(globalAK().config);
|
||||
this.host.config = globalAK().config;
|
||||
this.context.setValue(ServerContext.config);
|
||||
this.host.config = ServerContext.config;
|
||||
this.fetch = this.fetch.bind(this);
|
||||
this.fetch();
|
||||
}
|
||||
|
@ -23,20 +23,9 @@ const configContext = Symbol("configContext");
|
||||
const modalController = Symbol("modalController");
|
||||
const versionContext = Symbol("versionContext");
|
||||
|
||||
export abstract class LightInterface extends AKElement implements ThemedElement {
|
||||
export abstract class Interface extends AKElement implements ThemedElement {
|
||||
protected static readonly PFBaseStyleSheet = createStyleSheetUnsafe(PFBase);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const styleParent = resolveStyleSheetParent(document);
|
||||
|
||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||
|
||||
appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Interface extends LightInterface implements ThemedElement {
|
||||
[configContext]: ConfigContextController;
|
||||
|
||||
[modalController]: ModalOrchestrationController;
|
||||
@ -49,6 +38,12 @@ export abstract class Interface extends LightInterface implements ThemedElement
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const styleParent = resolveStyleSheetParent(document);
|
||||
|
||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||
|
||||
appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
|
||||
|
||||
this.addController(new BrandContextController(this));
|
||||
this[configContext] = new ConfigContextController(this);
|
||||
this[modalController] = new ModalOrchestrationController(this);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AuthenticatedInterface, Interface, LightInterface } from "./Interface";
|
||||
import { AuthenticatedInterface, Interface } from "./Interface";
|
||||
|
||||
export { Interface, AuthenticatedInterface, LightInterface };
|
||||
export { Interface, AuthenticatedInterface };
|
||||
export default Interface;
|
||||
|
@ -3,9 +3,10 @@ import {
|
||||
EVENT_WS_MESSAGE,
|
||||
TITLE_DEFAULT,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { UIConfig, UserDisplay, getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { UIConfig, UserDisplay } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import "@goauthentik/components/ak-nav-buttons";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
@ -404,7 +405,7 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.session}>
|
||||
<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/user/"
|
||||
href="${ServerContext.baseURL}if/user/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("User interface")}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
|
||||
import { LOCALES as RAW_LOCALES, enLocale } from "./definitions";
|
||||
import { AkLocale } from "./types";
|
||||
|
||||
@ -51,7 +49,7 @@ export function autoDetectLanguage(userReq = TOMBSTONE, brandReq = TOMBSTONE): s
|
||||
userReq,
|
||||
window.navigator?.language ?? TOMBSTONE,
|
||||
brandReq,
|
||||
globalAK()?.locale ?? TOMBSTONE,
|
||||
document.documentElement.getAttribute("lang") ?? TOMBSTONE,
|
||||
DEFAULT_LOCALE,
|
||||
].filter(isLocaleCandidate);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
|
||||
@ -76,7 +76,7 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
|
||||
: "pf-m-gold"}"
|
||||
>
|
||||
${message}
|
||||
<a href="${globalAK().api.base}if/admin/#/enterprise/licenses"
|
||||
<a href="${ServerContext.baseURL}if/admin/#/enterprise/licenses"
|
||||
>${msg("Click here for more info.")}</a
|
||||
>
|
||||
</div>`;
|
||||
|
@ -230,7 +230,9 @@ export abstract class AKChart<T> extends AKElement {
|
||||
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
||||
</ak-empty-state>
|
||||
`
|
||||
: html`${this.chart ? html`` : html`<ak-empty-state loading></ak-empty-state>`}`}
|
||||
: html`${this.chart
|
||||
? html``
|
||||
: html`<ak-empty-state ?loading="${true}"></ak-empty-state>`}`}
|
||||
${this.centerText ? html` <span>${this.centerText}</span> ` : html``}
|
||||
<canvas style="${this.chart === undefined ? "display: none;" : ""}"></canvas>
|
||||
</div>
|
||||
|
@ -71,7 +71,7 @@ export abstract class ModelForm<T, PKT extends string | number> extends Form<T>
|
||||
|
||||
renderVisible(): TemplateResult {
|
||||
if ((this._instancePk && !this.instance) || !this._initialDataLoad) {
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
}
|
||||
return super.renderVisible();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RequestInfo } from "@goauthentik/common/api/middleware";
|
||||
import { EVENT_API_DRAWER_TOGGLE, EVENT_REQUEST_POST } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
|
||||
@ -92,7 +92,7 @@ export class APIDrawer extends AKElement {
|
||||
<h1 class="pf-c-notification-drawer__header-title">
|
||||
${msg("API Requests")}
|
||||
</h1>
|
||||
<a href="${globalAK().api.base}api/v3/" target="_blank"
|
||||
<a href="${ServerContext.baseURL}api/v3/" target="_blank"
|
||||
>${msg("Open API Browser")}</a
|
||||
>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { actionToLabel } from "@goauthentik/common/labels";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
@ -99,7 +99,7 @@ export class NotificationDrawer extends AKElement {
|
||||
html`
|
||||
<a
|
||||
class="pf-c-dropdown__toggle pf-m-plain"
|
||||
href="${globalAK().api.base}if/admin/#/events/log/${item.event?.pk}"
|
||||
href="${ServerContext.baseURL}if/admin/#/events/log/${item.event?.pk}"
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Show details")}>
|
||||
<i class="fas fa-share-square"></i>
|
||||
|
@ -51,7 +51,7 @@ export class Route {
|
||||
if (this.callback) {
|
||||
return html`${until(
|
||||
this.callback(args),
|
||||
html`<ak-empty-state loading></ak-empty-state>`,
|
||||
html`<ak-empty-state ?loading=${true}></ak-empty-state>`,
|
||||
)}`;
|
||||
}
|
||||
if (this.element) {
|
||||
|
@ -6,35 +6,19 @@ import { TemplateResult } from "lit";
|
||||
export class RouteMatch {
|
||||
route: Route;
|
||||
arguments: { [key: string]: string };
|
||||
fullURL: string;
|
||||
fullUrl?: string;
|
||||
|
||||
constructor(route: Route, fullUrl: string) {
|
||||
constructor(route: Route) {
|
||||
this.route = route;
|
||||
this.arguments = {};
|
||||
this.fullURL = fullUrl;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return this.route.render(this.arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the matched Route's URL regex to a sanitized, readable URL by replacing
|
||||
* all regex values with placeholders according to the name of their regex group.
|
||||
*
|
||||
* @returns The sanitized URL for logging/tracing.
|
||||
*/
|
||||
sanitizedURL() {
|
||||
let cleanedURL = this.fullURL;
|
||||
for (const match of Object.keys(this.arguments)) {
|
||||
const value = this.arguments[match];
|
||||
cleanedURL = cleanedURL?.replace(value, `:${match}`);
|
||||
}
|
||||
return cleanedURL;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `<RouteMatch url=${this.sanitizedURL()} route=${this.route} arguments=${JSON.stringify(
|
||||
return `<RouteMatch url=${this.fullUrl} route=${this.route} arguments=${JSON.stringify(
|
||||
this.arguments,
|
||||
)}>`;
|
||||
}
|
||||
|
@ -3,15 +3,8 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { Route } from "@goauthentik/elements/router/Route";
|
||||
import { RouteMatch } from "@goauthentik/elements/router/RouteMatch";
|
||||
import "@goauthentik/elements/router/Router404";
|
||||
import {
|
||||
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
|
||||
getClient,
|
||||
startBrowserTracingNavigationSpan,
|
||||
startBrowserTracingPageLoadSpan,
|
||||
} from "@sentry/browser";
|
||||
import { Client, Span } from "@sentry/types";
|
||||
|
||||
import { CSSResult, PropertyValues, TemplateResult, css, html } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
// Poliyfill for hashchange.newURL,
|
||||
@ -60,9 +53,6 @@ export class RouterOutlet extends AKElement {
|
||||
@property({ attribute: false })
|
||||
routes: Route[] = [];
|
||||
|
||||
private sentryClient?: Client;
|
||||
private pageLoadSpan?: Span;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
@ -79,15 +69,6 @@ export class RouterOutlet extends AKElement {
|
||||
constructor() {
|
||||
super();
|
||||
window.addEventListener("hashchange", (ev: HashChangeEvent) => this.navigate(ev));
|
||||
this.sentryClient = getClient();
|
||||
if (this.sentryClient) {
|
||||
this.pageLoadSpan = startBrowserTracingPageLoadSpan(this.sentryClient, {
|
||||
name: window.location.pathname,
|
||||
attributes: {
|
||||
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
@ -111,8 +92,9 @@ export class RouterOutlet extends AKElement {
|
||||
this.routes.some((route) => {
|
||||
const match = route.url.exec(activeUrl);
|
||||
if (match !== null) {
|
||||
matchedRoute = new RouteMatch(route, activeUrl);
|
||||
matchedRoute = new RouteMatch(route);
|
||||
matchedRoute.arguments = match.groups || {};
|
||||
matchedRoute.fullUrl = activeUrl;
|
||||
console.debug("authentik/router: found match ", matchedRoute);
|
||||
return true;
|
||||
}
|
||||
@ -125,31 +107,13 @@ export class RouterOutlet extends AKElement {
|
||||
<ak-router-404 url=${activeUrl}></ak-router-404>
|
||||
</div>`;
|
||||
});
|
||||
matchedRoute = new RouteMatch(route, activeUrl);
|
||||
matchedRoute = new RouteMatch(route);
|
||||
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
|
||||
matchedRoute.fullUrl = activeUrl;
|
||||
}
|
||||
this.current = matchedRoute;
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>): void {
|
||||
if (!changedProperties.has("current") || !this.current) return;
|
||||
if (!this.sentryClient) return;
|
||||
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
|
||||
if (this.pageLoadSpan) {
|
||||
this.pageLoadSpan.updateName(this.current.sanitizedURL());
|
||||
this.pageLoadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "route");
|
||||
this.pageLoadSpan = undefined;
|
||||
} else {
|
||||
startBrowserTracingNavigationSpan(this.sentryClient, {
|
||||
op: "navigation",
|
||||
name: this.current.sanitizedURL(),
|
||||
attributes: {
|
||||
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render(): TemplateResult | undefined {
|
||||
return this.current?.render();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { AdminInterface } from "@goauthentik/admin/AdminInterface/index.entrypoint.js";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { AKElement, rootInterface } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
@ -45,7 +45,7 @@ export class SidebarVersion extends WithLicenseSummary(WithVersion(AKElement)) {
|
||||
if (!this.version || !this.licenseSummary) {
|
||||
return nothing;
|
||||
}
|
||||
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
let product = ServerContext.brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
|
||||
product += ` ${msg("Enterprise")}`;
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ export class SyncStatusCard extends AKElement {
|
||||
|
||||
renderSyncStatus(): TemplateResult {
|
||||
if (this.loading) {
|
||||
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
}
|
||||
if (!this.syncState) {
|
||||
return html`${msg("No sync status.")}`;
|
||||
|
@ -19,7 +19,7 @@ describe("ak-empty-state", () => {
|
||||
});
|
||||
|
||||
it("should render the default loader", async () => {
|
||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
||||
render(html`<ak-empty-state ?loading=${true} header=${msg("Loading")}> </ak-empty-state>`);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
@ -139,7 +139,8 @@ export class UserSourceSettingsPage extends AKElement {
|
||||
})}
|
||||
`}
|
||||
`
|
||||
: html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`}
|
||||
: html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
||||
</ak-empty-state>`}
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
EVENT_FLOW_INSPECTOR_TOGGLE,
|
||||
TITLE_DEFAULT,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { tryInitializeSentry } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
@ -171,7 +171,6 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
|
||||
constructor() {
|
||||
configureSentry();
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
const inspector = new URL(window.location.toString()).searchParams.get("inspector");
|
||||
@ -238,9 +237,12 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
tryInitializeSentry(ServerContext.config);
|
||||
|
||||
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
this.inspectorAvailable = true;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
|
||||
@ -481,7 +483,8 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
|
||||
getLayout(): string {
|
||||
const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
|
||||
const prefilledFlow = ServerContext.flowLayout || FlowLayoutEnum.Stacked;
|
||||
|
||||
if (this.challenge) {
|
||||
return this.challenge?.flowInfo?.layout || prefilledFlow;
|
||||
}
|
||||
@ -521,7 +524,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
<img
|
||||
src="${themeImage(
|
||||
this.brand?.brandingLogo ??
|
||||
globalAK()?.brand.brandingLogo ??
|
||||
ServerContext.brand.brandingLogo ??
|
||||
DefaultBrand.brandingLogo,
|
||||
)}"
|
||||
alt="${msg("authentik Logo")}"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/flow/FormStatic";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
|
||||
@ -24,7 +24,8 @@ export class SessionEnd extends BaseStage<SessionEndChallenge, unknown> {
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.challenge) {
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
return html`<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
||||
@ -47,7 +48,7 @@ export class SessionEnd extends BaseStage<SessionEndChallenge, unknown> {
|
||||
str`You've logged out of ${this.challenge.applicationName}. You can go back to the overview to launch another application, or log out of your authentik account.`,
|
||||
)}
|
||||
</p>
|
||||
<a href="${globalAK().api.base}" class="pf-c-button pf-m-primary">
|
||||
<a href="${ServerContext.baseURL}" class="pf-c-button pf-m-primary">
|
||||
${msg("Go back to overview")}
|
||||
</a>
|
||||
${this.challenge.invalidationFlowUrl
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { LightInterface } from "@goauthentik/elements/Interface";
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
@ -10,7 +10,7 @@ import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-loading")
|
||||
export class Loading extends LightInterface {
|
||||
export class Loading extends Interface {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
@ -25,6 +25,16 @@ export class Loading extends LightInterface {
|
||||
];
|
||||
}
|
||||
|
||||
registerContexts(): void {
|
||||
// Stub function to avoid making API requests for things we don't need. The `Interface` base class loads
|
||||
// a bunch of data that is used globally by various things, however this is an interface that is shown
|
||||
// very briefly and we don't need any of that data.
|
||||
}
|
||||
|
||||
async _initCustomCSS(): Promise<void> {
|
||||
// Stub function to avoid fetching custom CSS.
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <section
|
||||
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PFSize } from "@goauthentik/common/enums.js";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { truncateWords } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/AppIcon";
|
||||
import { AKElement, rootInterface } from "@goauthentik/elements/Base";
|
||||
@ -82,8 +82,7 @@ export class LibraryApplication extends AKElement {
|
||||
? html`
|
||||
<a
|
||||
class="pf-c-button pf-m-control pf-m-small pf-m-block"
|
||||
href="${globalAK().api
|
||||
.base}if/admin/#/core/applications/${application?.slug}"
|
||||
href="${ServerContext.baseURL}if/admin/#/core/applications/${application?.slug}"
|
||||
>
|
||||
<i class="fas fa-edit"></i> ${msg("Edit")}
|
||||
</a>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { docLink, globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext, docLink } from "@goauthentik/common/server-context";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
|
||||
@ -49,7 +49,7 @@ export class LibraryPageApplicationEmptyList extends AKElement {
|
||||
<a
|
||||
aria-disabled="false"
|
||||
class="cta pf-c-button pf-m-secondary"
|
||||
href="${globalAK().api.base}if/admin/${href}"
|
||||
href="${ServerContext.baseURL}if/admin/${href}"
|
||||
>${msg("Create a new application")}</a
|
||||
>
|
||||
</div>
|
||||
|
@ -102,7 +102,7 @@ export class LibraryPage extends AKElement {
|
||||
}
|
||||
|
||||
loading() {
|
||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> </ak-empty-state>`;
|
||||
}
|
||||
|
||||
running() {
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
EVENT_WS_MESSAGE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { setSentryPII, tryInitializeSentry } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { UIConfig, getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
@ -170,14 +170,14 @@ class UserInterfacePresentation extends AKElement {
|
||||
|
||||
return html`<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/admin/"
|
||||
href="${ServerContext.baseURL}if/admin/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("Admin interface")}
|
||||
</a>
|
||||
<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none-on-md pf-u-display-block"
|
||||
href="${globalAK().api.base}if/admin/"
|
||||
href="${ServerContext.baseURL}if/admin/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("Admin")}
|
||||
@ -281,10 +281,12 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
me?: SessionUser;
|
||||
|
||||
constructor() {
|
||||
configureSentry(true);
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
this.fetchConfigurationDetails();
|
||||
|
||||
tryInitializeSentry(ServerContext.config);
|
||||
|
||||
this.toggleNotificationDrawer = this.toggleNotificationDrawer.bind(this);
|
||||
this.toggleApiDrawer = this.toggleApiDrawer.bind(this);
|
||||
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
|
||||
@ -325,6 +327,8 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
this.me = session;
|
||||
this.uiConfig = getConfigForUser(session.user);
|
||||
|
||||
setSentryPII(session.user);
|
||||
|
||||
new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsNotificationsList({
|
||||
seen: false,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user