Compare commits
102 Commits
monorepo-d
...
core/fix-g
Author | SHA1 | Date | |
---|---|---|---|
af9ce90b8b | |||
05e5c6309c | |||
ad4a765a80 | |||
4dcd481010 | |||
d0dc14d84d | |||
7bf960352b | |||
c07d01661b | |||
427597ec14 | |||
7cc77bd387 | |||
381a1a2c49 | |||
08f8222224 | |||
1211c34a18 | |||
22efb57369 | |||
3eeda53be6 | |||
82ace18703 | |||
8589079252 | |||
ae2af6e58e | |||
86a7f98ff6 | |||
3af45371d3 | |||
b01ffd934f | |||
f11ba94603 | |||
7d2aa43364 | |||
f1351a7577 | |||
0611eea0e7 | |||
d0b46fcf9c | |||
dcbdc37d31 | |||
d07f396379 | |||
0972103b83 | |||
b448e76db4 | |||
f2937bd6dd | |||
53c2e3e77c | |||
7dd62c1f55 | |||
33e3510fba | |||
0e5fac2642 | |||
c53b1fe78a | |||
838a7457b2 | |||
a3c07bc9ff | |||
121f2c609d | |||
365affc28e | |||
f367822779 | |||
848198125d | |||
497ac5e3d0 | |||
1773d4d681 | |||
4edbb51939 | |||
c7e97ab48e | |||
31f7faae1c | |||
f5dae2ae92 | |||
2c043dba0b | |||
bda10e5db1 | |||
be9ae7d4f7 | |||
b4a6189bfa | |||
bfdb827ff9 | |||
488a58e1c5 | |||
3f83e69453 | |||
e92fa5df0b | |||
f8c22170df | |||
e3d08a8434 | |||
97d3e9afdc | |||
1eb08def73 | |||
6e3b379e4a | |||
264f59775c | |||
d048f1ecbd | |||
eb31f31584 | |||
fe5c842e92 | |||
b82d3100c9 | |||
49bb668036 | |||
52c70c7700 | |||
b99fd36f86 | |||
8a5381eca3 | |||
2c77830179 | |||
ffcd7def60 | |||
ed121bc2a3 | |||
d5ab9d9167 | |||
a983321ad6 | |||
9c3420ede4 | |||
91b40350aa | |||
1912991682 | |||
71b9117f53 | |||
b5f947f460 | |||
3a2f7e9549 | |||
1582ce0920 | |||
6d3eea5266 | |||
e987208bd1 | |||
0efab8eef7 | |||
9402dac8ae | |||
f57a290eee | |||
5dab0d2b7a | |||
2da6036248 | |||
cdba94cea4 | |||
c59eca664a | |||
d5b205f9c0 | |||
8ad9ad833e | |||
599ce15f68 | |||
91310eff52 | |||
b522d6732a | |||
17d96f204e | |||
65e4667bc3 | |||
f67f9e5ed0 | |||
62dd6a4393 | |||
a46eae8276 | |||
c4acc9fc24 | |||
e748a03082 |
3
.github/workflows/ci-main.yml
vendored
3
.github/workflows/ci-main.yml
vendored
@ -200,7 +200,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: web/dist
|
path: web/dist
|
||||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
|
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
|
||||||
- name: prepare web ui
|
- name: prepare web ui
|
||||||
if: steps.cache-web.outputs.cache-hit != 'true'
|
if: steps.cache-web.outputs.cache-hit != 'true'
|
||||||
working-directory: web
|
working-directory: web
|
||||||
@ -208,6 +208,7 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
make -C .. gen-client-ts
|
make -C .. gen-client-ts
|
||||||
npm run build
|
npm run build
|
||||||
|
npm run build:sfe
|
||||||
- name: run e2e
|
- name: run e2e
|
||||||
run: |
|
run: |
|
||||||
uv run coverage run manage.py test ${{ matrix.job.glob }}
|
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
|
- name: Generate API
|
||||||
run: make gen-client-go
|
run: make gen-client-go
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v7
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout 5000s --verbose
|
args: --timeout 5000s --verbose
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -40,7 +40,8 @@ COPY ./web /work/web/
|
|||||||
COPY ./website /work/website/
|
COPY ./website /work/website/
|
||||||
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build && \
|
||||||
|
npm run build:sfe
|
||||||
|
|
||||||
# Stage 3: Build go proxy
|
# Stage 3: Build go proxy
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
||||||
@ -85,18 +86,17 @@ FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip
|
|||||||
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
|
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
|
||||||
ENV GEOIPUPDATE_VERBOSE="1"
|
ENV GEOIPUPDATE_VERBOSE="1"
|
||||||
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
|
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
|
||||||
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"
|
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||||
--mount=type=secret,id=GEOIPUPDATE_LICENSE_KEY \
|
--mount=type=secret,id=GEOIPUPDATE_LICENSE_KEY \
|
||||||
mkdir -p /usr/share/GeoIP && \
|
mkdir -p /usr/share/GeoIP && \
|
||||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||||
|
|
||||||
# Stage 5: Download uv
|
# Stage 5: Download uv
|
||||||
FROM ghcr.io/astral-sh/uv:0.7.2 AS uv
|
FROM ghcr.io/astral-sh/uv:0.7.3 AS uv
|
||||||
# Stage 6: Base python image
|
# Stage 6: Base python image
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
|
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
|
||||||
|
|
||||||
ENV VENV_PATH="/ak-root/.venv" \
|
ENV VENV_PATH="/ak-root/.venv" \
|
||||||
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
||||||
|
@ -42,4 +42,4 @@ See [SECURITY.md](SECURITY.md)
|
|||||||
|
|
||||||
## Adoption and Contributions
|
## Adoption and Contributions
|
||||||
|
|
||||||
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [CONTRIBUTING.md file](./CONTRIBUTING.md).
|
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [contribution guide](https://docs.goauthentik.io/docs/developer-docs?utm_source=github).
|
||||||
|
@ -54,7 +54,7 @@ def create_component(generator: SchemaGenerator, name, schema, type_=ResolvedCom
|
|||||||
return component
|
return component
|
||||||
|
|
||||||
|
|
||||||
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs): # noqa: W0613
|
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs):
|
||||||
"""Workaround to set a default response for endpoints.
|
"""Workaround to set a default response for endpoints.
|
||||||
Workaround suggested at
|
Workaround suggested at
|
||||||
<https://github.com/tfranzel/drf-spectacular/issues/119#issuecomment-656970357>
|
<https://github.com/tfranzel/drf-spectacular/issues/119#issuecomment-656970357>
|
||||||
|
@ -164,9 +164,7 @@ class BlueprintEntry:
|
|||||||
"""Get the blueprint model, with yaml tags resolved if present"""
|
"""Get the blueprint model, with yaml tags resolved if present"""
|
||||||
return str(self.tag_resolver(self.model, blueprint))
|
return str(self.tag_resolver(self.model, blueprint))
|
||||||
|
|
||||||
def get_permissions(
|
def get_permissions(self, blueprint: "Blueprint") -> Generator[BlueprintEntryPermission]:
|
||||||
self, blueprint: "Blueprint"
|
|
||||||
) -> Generator[BlueprintEntryPermission, None, None]:
|
|
||||||
"""Get permissions of this entry, with all yaml tags resolved"""
|
"""Get permissions of this entry, with all yaml tags resolved"""
|
||||||
for perm in self.permissions:
|
for perm in self.permissions:
|
||||||
yield BlueprintEntryPermission(
|
yield BlueprintEntryPermission(
|
||||||
|
@ -5,10 +5,10 @@ from typing import Any
|
|||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.db.models import Value as V
|
from django.db.models import Value as V
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from sentry_sdk import get_current_span
|
|
||||||
|
|
||||||
from authentik import get_full_version
|
from authentik import get_full_version
|
||||||
from authentik.brands.models import Brand
|
from authentik.brands.models import Brand
|
||||||
|
from authentik.lib.sentry import get_http_meta
|
||||||
from authentik.tenants.models import Tenant
|
from authentik.tenants.models import Tenant
|
||||||
|
|
||||||
_q_default = Q(default=True)
|
_q_default = Q(default=True)
|
||||||
@ -32,13 +32,9 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
|
|||||||
"""Context Processor that injects brand object into every template"""
|
"""Context Processor that injects brand object into every template"""
|
||||||
brand = getattr(request, "brand", DEFAULT_BRAND)
|
brand = getattr(request, "brand", DEFAULT_BRAND)
|
||||||
tenant = getattr(request, "tenant", Tenant())
|
tenant = getattr(request, "tenant", Tenant())
|
||||||
trace = ""
|
|
||||||
span = get_current_span()
|
|
||||||
if span:
|
|
||||||
trace = span.to_traceparent()
|
|
||||||
return {
|
return {
|
||||||
"brand": brand,
|
"brand": brand,
|
||||||
"footer_links": tenant.footer_links,
|
"footer_links": tenant.footer_links,
|
||||||
"sentry_trace": trace,
|
"html_meta": {**get_http_meta()},
|
||||||
"version": get_full_version(),
|
"version": get_full_version(),
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,12 @@ from drf_spectacular.utils import (
|
|||||||
from guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, IntegerField, SerializerMethodField
|
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.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ListSerializer, ValidationError
|
from rest_framework.serializers import ListSerializer, ValidationError
|
||||||
from rest_framework.validators import UniqueValidator
|
from rest_framework.validators import UniqueValidator
|
||||||
|
from rest_framework.views import View
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
@ -85,34 +87,6 @@ class GroupSerializer(ModelSerializer):
|
|||||||
raise ValidationError(_("Cannot set group as parent of itself."))
|
raise ValidationError(_("Cannot set group as parent of itself."))
|
||||||
return parent
|
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:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = [
|
fields = [
|
||||||
@ -180,6 +154,36 @@ class GroupFilter(FilterSet):
|
|||||||
fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"]
|
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):
|
class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||||
"""Group Viewset"""
|
"""Group Viewset"""
|
||||||
|
|
||||||
@ -192,6 +196,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
|||||||
serializer_class = GroupSerializer
|
serializer_class = GroupSerializer
|
||||||
search_fields = ["name", "is_superuser"]
|
search_fields = ["name", "is_superuser"]
|
||||||
filterset_class = GroupFilter
|
filterset_class = GroupFilter
|
||||||
|
permission_classes = [SuperuserSetter]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.auth.management import create_permissions
|
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 django.core.management.base import BaseCommand, no_translations
|
||||||
from guardian.management import create_anonymous_user
|
from guardian.management import create_anonymous_user
|
||||||
|
|
||||||
@ -16,6 +17,10 @@ class Command(BaseCommand):
|
|||||||
"""Check permissions for all apps"""
|
"""Check permissions for all apps"""
|
||||||
for tenant in Tenant.objects.filter(ready=True):
|
for tenant in Tenant.objects.filter(ready=True):
|
||||||
with tenant:
|
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():
|
for app in apps.get_app_configs():
|
||||||
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
|
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
|
||||||
create_permissions(app, verbosity=0)
|
create_permissions(app, verbosity=0)
|
||||||
|
@ -31,7 +31,10 @@ class PickleSerializer:
|
|||||||
|
|
||||||
def loads(self, data):
|
def loads(self, data):
|
||||||
"""Unpickle data to be loaded from redis"""
|
"""Unpickle data to be loaded from redis"""
|
||||||
return pickle.loads(data) # nosec
|
try:
|
||||||
|
return pickle.loads(data) # nosec
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _migrate_session(
|
def _migrate_session(
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.9 on 2025-05-14 11:15
|
||||||
|
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def remove_old_authenticated_session_content_type(
|
||||||
|
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
|
|
||||||
|
ContentType.objects.using(db_alias).filter(model="oldauthenticatedsession").delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_core", "0047_delete_oldauthenticatedsession"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
code=remove_old_authenticated_session_content_type,
|
||||||
|
),
|
||||||
|
]
|
@ -21,7 +21,9 @@
|
|||||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
{% for key, value in html_meta.items %}
|
||||||
|
<meta name="{{key}}" content="{{ value }}" />
|
||||||
|
{% endfor %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
@ -118,12 +118,25 @@ class TestGroupsAPI(APITestCase):
|
|||||||
reverse("authentik_api:group-list"),
|
reverse("authentik_api:group-list"),
|
||||||
data={"name": generate_id(), "is_superuser": True},
|
data={"name": generate_id(), "is_superuser": True},
|
||||||
)
|
)
|
||||||
self.assertEqual(res.status_code, 400)
|
self.assertEqual(res.status_code, 403)
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
res.content,
|
res.content,
|
||||||
{"is_superuser": ["User does not have permission to set superuser status to True."]},
|
{"detail": "User does not have permission to set the given superuser status."},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_superuser_update_object_perm(self):
|
||||||
|
"""Test updating a superuser group with object permission"""
|
||||||
|
group = Group.objects.create(name=generate_id(), is_superuser=False)
|
||||||
|
assign_perm("view_group", self.login_user, group)
|
||||||
|
assign_perm("change_group", self.login_user, group)
|
||||||
|
assign_perm("enable_group_superuser", self.login_user, group)
|
||||||
|
self.client.force_login(self.login_user)
|
||||||
|
res = self.client.patch(
|
||||||
|
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||||
|
data={"is_superuser": True},
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
|
||||||
def test_superuser_update_no_perm(self):
|
def test_superuser_update_no_perm(self):
|
||||||
"""Test updating a superuser group without permission"""
|
"""Test updating a superuser group without permission"""
|
||||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
||||||
@ -134,10 +147,10 @@ class TestGroupsAPI(APITestCase):
|
|||||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||||
data={"is_superuser": False},
|
data={"is_superuser": False},
|
||||||
)
|
)
|
||||||
self.assertEqual(res.status_code, 400)
|
self.assertEqual(res.status_code, 403)
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
res.content,
|
res.content,
|
||||||
{"is_superuser": ["User does not have permission to set superuser status to False."]},
|
{"detail": "User does not have permission to set the given superuser status."},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_superuser_update_no_change(self):
|
def test_superuser_update_no_change(self):
|
||||||
@ -163,3 +176,27 @@ class TestGroupsAPI(APITestCase):
|
|||||||
data={"name": generate_id(), "is_superuser": True},
|
data={"name": generate_id(), "is_superuser": True},
|
||||||
)
|
)
|
||||||
self.assertEqual(res.status_code, 201)
|
self.assertEqual(res.status_code, 201)
|
||||||
|
|
||||||
|
def test_superuser_create_no_perm(self):
|
||||||
|
"""Test creating a superuser group with no permission"""
|
||||||
|
assign_perm("authentik_core.add_group", self.login_user)
|
||||||
|
self.client.force_login(self.login_user)
|
||||||
|
res = self.client.post(
|
||||||
|
reverse("authentik_api:group-list"),
|
||||||
|
data={"name": generate_id(), "is_superuser": True},
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 403)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
res.content,
|
||||||
|
{"detail": "User does not have permission to set the given superuser status."},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_no_superuser_create_no_perm(self):
|
||||||
|
"""Test creating a non-superuser group with no permission"""
|
||||||
|
assign_perm("authentik_core.add_group", self.login_user)
|
||||||
|
self.client.force_login(self.login_user)
|
||||||
|
res = self.client.post(
|
||||||
|
reverse("authentik_api:group-list"),
|
||||||
|
data={"name": generate_id()},
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 201)
|
||||||
|
@ -132,13 +132,14 @@ class LicenseKey:
|
|||||||
"""Get a summarized version of all (not expired) licenses"""
|
"""Get a summarized version of all (not expired) licenses"""
|
||||||
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
||||||
for lic in License.objects.all():
|
for lic in License.objects.all():
|
||||||
total.internal_users += lic.internal_users
|
if lic.is_valid:
|
||||||
total.external_users += lic.external_users
|
total.internal_users += lic.internal_users
|
||||||
|
total.external_users += lic.external_users
|
||||||
|
total.license_flags.extend(lic.status.license_flags)
|
||||||
exp_ts = int(mktime(lic.expiry.timetuple()))
|
exp_ts = int(mktime(lic.expiry.timetuple()))
|
||||||
if total.exp == 0:
|
if total.exp == 0:
|
||||||
total.exp = exp_ts
|
total.exp = exp_ts
|
||||||
total.exp = max(total.exp, exp_ts)
|
total.exp = max(total.exp, exp_ts)
|
||||||
total.license_flags.extend(lic.status.license_flags)
|
|
||||||
return total
|
return total
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -39,6 +39,10 @@ class License(SerializerModel):
|
|||||||
internal_users = models.BigIntegerField()
|
internal_users = models.BigIntegerField()
|
||||||
external_users = models.BigIntegerField()
|
external_users = models.BigIntegerField()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
return self.expiry >= now()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[BaseSerializer]:
|
def serializer(self) -> type[BaseSerializer]:
|
||||||
from authentik.enterprise.api import LicenseSerializer
|
from authentik.enterprise.api import LicenseSerializer
|
||||||
|
@ -8,6 +8,7 @@ from django.test import TestCase
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from authentik.core.models import User
|
||||||
from authentik.enterprise.license import LicenseKey
|
from authentik.enterprise.license import LicenseKey
|
||||||
from authentik.enterprise.models import (
|
from authentik.enterprise.models import (
|
||||||
THRESHOLD_READ_ONLY_WEEKS,
|
THRESHOLD_READ_ONLY_WEEKS,
|
||||||
@ -71,9 +72,9 @@ class TestEnterpriseLicense(TestCase):
|
|||||||
)
|
)
|
||||||
def test_valid_multiple(self):
|
def test_valid_multiple(self):
|
||||||
"""Check license verification"""
|
"""Check license verification"""
|
||||||
lic = License.objects.create(key=generate_id())
|
lic = License.objects.create(key=generate_id(), expiry=expiry_valid)
|
||||||
self.assertTrue(lic.status.status().is_valid)
|
self.assertTrue(lic.status.status().is_valid)
|
||||||
lic2 = License.objects.create(key=generate_id())
|
lic2 = License.objects.create(key=generate_id(), expiry=expiry_valid)
|
||||||
self.assertTrue(lic2.status.status().is_valid)
|
self.assertTrue(lic2.status.status().is_valid)
|
||||||
total = LicenseKey.get_total()
|
total = LicenseKey.get_total()
|
||||||
self.assertEqual(total.internal_users, 200)
|
self.assertEqual(total.internal_users, 200)
|
||||||
@ -232,7 +233,9 @@ class TestEnterpriseLicense(TestCase):
|
|||||||
)
|
)
|
||||||
def test_expiry_expired(self):
|
def test_expiry_expired(self):
|
||||||
"""Check license verification"""
|
"""Check license verification"""
|
||||||
License.objects.create(key=generate_id())
|
User.objects.all().delete()
|
||||||
|
License.objects.all().delete()
|
||||||
|
License.objects.create(key=generate_id(), expiry=expiry_expired)
|
||||||
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRED)
|
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRED)
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
|
@ -57,7 +57,7 @@ class LogEventSerializer(PassiveSerializer):
|
|||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]:
|
def capture_logs(log_default_output=True) -> Generator[list[LogEvent]]:
|
||||||
"""Capture log entries created"""
|
"""Capture log entries created"""
|
||||||
logs = []
|
logs = []
|
||||||
cap = LogCapture()
|
cap = LogCapture()
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
||||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||||
|
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||||
{% include "base/header_js.html" %}
|
{% include "base/header_js.html" %}
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
@ -22,7 +23,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background-image: url("{{ flow.background_url }}");
|
background-image: url("{{ flow_background_url }}");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{% block head_before %}
|
{% block head_before %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link rel="prefetch" href="{{ flow.background_url }}" />
|
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||||
{% if flow.compatibility_mode and not inspector %}
|
{% if flow.compatibility_mode and not inspector %}
|
||||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -21,7 +21,7 @@ window.authentik.flow = {
|
|||||||
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--ak-flow-background: url("{{ flow.background_url }}");
|
--ak-flow-background: url("{{ flow_background_url }}");
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -13,7 +13,9 @@ class FlowInterfaceView(InterfaceView):
|
|||||||
"""Flow interface"""
|
"""Flow interface"""
|
||||||
|
|
||||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||||
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||||
|
kwargs["flow"] = flow
|
||||||
|
kwargs["flow_background_url"] = flow.background_url(self.request)
|
||||||
kwargs["inspector"] = "inspector" in self.request.GET
|
kwargs["inspector"] = "inspector" in self.request.GET
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
@ -363,6 +363,9 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
|
|||||||
pool_options = config.get_dict_from_b64_json("postgresql.pool_options", True)
|
pool_options = config.get_dict_from_b64_json("postgresql.pool_options", True)
|
||||||
if not pool_options:
|
if not pool_options:
|
||||||
pool_options = True
|
pool_options = True
|
||||||
|
# FIXME: Temporarily force pool to be deactivated.
|
||||||
|
# See https://github.com/goauthentik/authentik/issues/14320
|
||||||
|
pool_options = False
|
||||||
|
|
||||||
db = {
|
db = {
|
||||||
"default": {
|
"default": {
|
||||||
|
@ -17,7 +17,7 @@ from ldap3.core.exceptions import LDAPException
|
|||||||
from redis.exceptions import ConnectionError as RedisConnectionError
|
from redis.exceptions import ConnectionError as RedisConnectionError
|
||||||
from redis.exceptions import RedisError, ResponseError
|
from redis.exceptions import RedisError, ResponseError
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from sentry_sdk import HttpTransport
|
from sentry_sdk import HttpTransport, get_current_scope
|
||||||
from sentry_sdk import init as sentry_sdk_init
|
from sentry_sdk import init as sentry_sdk_init
|
||||||
from sentry_sdk.api import set_tag
|
from sentry_sdk.api import set_tag
|
||||||
from sentry_sdk.integrations.argv import ArgvIntegration
|
from sentry_sdk.integrations.argv import ArgvIntegration
|
||||||
@ -27,6 +27,7 @@ from sentry_sdk.integrations.redis import RedisIntegration
|
|||||||
from sentry_sdk.integrations.socket import SocketIntegration
|
from sentry_sdk.integrations.socket import SocketIntegration
|
||||||
from sentry_sdk.integrations.stdlib import StdlibIntegration
|
from sentry_sdk.integrations.stdlib import StdlibIntegration
|
||||||
from sentry_sdk.integrations.threading import ThreadingIntegration
|
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 structlog.stdlib import get_logger
|
||||||
from websockets.exceptions import WebSocketException
|
from websockets.exceptions import WebSocketException
|
||||||
|
|
||||||
@ -95,6 +96,8 @@ def traces_sampler(sampling_context: dict) -> float:
|
|||||||
return 0
|
return 0
|
||||||
if _type == "websocket":
|
if _type == "websocket":
|
||||||
return 0
|
return 0
|
||||||
|
if CONFIG.get_bool("debug"):
|
||||||
|
return 1
|
||||||
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
||||||
|
|
||||||
|
|
||||||
@ -167,3 +170,14 @@ def before_send(event: dict, hint: dict) -> dict | None:
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
return None
|
return None
|
||||||
return event
|
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,
|
request: HttpRequest | None,
|
||||||
return_mapping: bool = False,
|
return_mapping: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Generator[tuple[dict, PropertyMapping], None]:
|
) -> Generator[tuple[dict, PropertyMapping]]:
|
||||||
"""Iterate over all mappings that were pre-compiled and
|
"""Iterate over all mappings that were pre-compiled and
|
||||||
execute all of them with the given context"""
|
execute all of them with the given context"""
|
||||||
if not self.__has_compiled:
|
if not self.__has_compiled:
|
||||||
|
@ -494,86 +494,88 @@ class TestConfig(TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_db_pool(self):
|
# FIXME: Temporarily force pool to be deactivated.
|
||||||
"""Test DB Config with pool"""
|
# See https://github.com/goauthentik/authentik/issues/14320
|
||||||
config = ConfigLoader()
|
# def test_db_pool(self):
|
||||||
config.set("postgresql.host", "foo")
|
# """Test DB Config with pool"""
|
||||||
config.set("postgresql.name", "foo")
|
# config = ConfigLoader()
|
||||||
config.set("postgresql.user", "foo")
|
# config.set("postgresql.host", "foo")
|
||||||
config.set("postgresql.password", "foo")
|
# config.set("postgresql.name", "foo")
|
||||||
config.set("postgresql.port", "foo")
|
# config.set("postgresql.user", "foo")
|
||||||
config.set("postgresql.test.name", "foo")
|
# config.set("postgresql.password", "foo")
|
||||||
config.set("postgresql.use_pool", True)
|
# config.set("postgresql.port", "foo")
|
||||||
conf = django_db_config(config)
|
# config.set("postgresql.test.name", "foo")
|
||||||
self.assertEqual(
|
# config.set("postgresql.use_pool", True)
|
||||||
conf,
|
# conf = django_db_config(config)
|
||||||
{
|
# self.assertEqual(
|
||||||
"default": {
|
# conf,
|
||||||
"ENGINE": "authentik.root.db",
|
# {
|
||||||
"HOST": "foo",
|
# "default": {
|
||||||
"NAME": "foo",
|
# "ENGINE": "authentik.root.db",
|
||||||
"OPTIONS": {
|
# "HOST": "foo",
|
||||||
"pool": True,
|
# "NAME": "foo",
|
||||||
"sslcert": None,
|
# "OPTIONS": {
|
||||||
"sslkey": None,
|
# "pool": True,
|
||||||
"sslmode": None,
|
# "sslcert": None,
|
||||||
"sslrootcert": None,
|
# "sslkey": None,
|
||||||
},
|
# "sslmode": None,
|
||||||
"PASSWORD": "foo",
|
# "sslrootcert": None,
|
||||||
"PORT": "foo",
|
# },
|
||||||
"TEST": {"NAME": "foo"},
|
# "PASSWORD": "foo",
|
||||||
"USER": "foo",
|
# "PORT": "foo",
|
||||||
"CONN_MAX_AGE": 0,
|
# "TEST": {"NAME": "foo"},
|
||||||
"CONN_HEALTH_CHECKS": False,
|
# "USER": "foo",
|
||||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
# "CONN_MAX_AGE": 0,
|
||||||
}
|
# "CONN_HEALTH_CHECKS": False,
|
||||||
},
|
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||||
)
|
# }
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
|
||||||
def test_db_pool_options(self):
|
# def test_db_pool_options(self):
|
||||||
"""Test DB Config with pool"""
|
# """Test DB Config with pool"""
|
||||||
config = ConfigLoader()
|
# config = ConfigLoader()
|
||||||
config.set("postgresql.host", "foo")
|
# config.set("postgresql.host", "foo")
|
||||||
config.set("postgresql.name", "foo")
|
# config.set("postgresql.name", "foo")
|
||||||
config.set("postgresql.user", "foo")
|
# config.set("postgresql.user", "foo")
|
||||||
config.set("postgresql.password", "foo")
|
# config.set("postgresql.password", "foo")
|
||||||
config.set("postgresql.port", "foo")
|
# config.set("postgresql.port", "foo")
|
||||||
config.set("postgresql.test.name", "foo")
|
# config.set("postgresql.test.name", "foo")
|
||||||
config.set("postgresql.use_pool", True)
|
# config.set("postgresql.use_pool", True)
|
||||||
config.set(
|
# config.set(
|
||||||
"postgresql.pool_options",
|
# "postgresql.pool_options",
|
||||||
base64.b64encode(
|
# base64.b64encode(
|
||||||
dumps(
|
# dumps(
|
||||||
{
|
# {
|
||||||
"max_size": 15,
|
# "max_size": 15,
|
||||||
}
|
# }
|
||||||
).encode()
|
# ).encode()
|
||||||
).decode(),
|
# ).decode(),
|
||||||
)
|
# )
|
||||||
conf = django_db_config(config)
|
# conf = django_db_config(config)
|
||||||
self.assertEqual(
|
# self.assertEqual(
|
||||||
conf,
|
# conf,
|
||||||
{
|
# {
|
||||||
"default": {
|
# "default": {
|
||||||
"ENGINE": "authentik.root.db",
|
# "ENGINE": "authentik.root.db",
|
||||||
"HOST": "foo",
|
# "HOST": "foo",
|
||||||
"NAME": "foo",
|
# "NAME": "foo",
|
||||||
"OPTIONS": {
|
# "OPTIONS": {
|
||||||
"pool": {
|
# "pool": {
|
||||||
"max_size": 15,
|
# "max_size": 15,
|
||||||
},
|
# },
|
||||||
"sslcert": None,
|
# "sslcert": None,
|
||||||
"sslkey": None,
|
# "sslkey": None,
|
||||||
"sslmode": None,
|
# "sslmode": None,
|
||||||
"sslrootcert": None,
|
# "sslrootcert": None,
|
||||||
},
|
# },
|
||||||
"PASSWORD": "foo",
|
# "PASSWORD": "foo",
|
||||||
"PORT": "foo",
|
# "PORT": "foo",
|
||||||
"TEST": {"NAME": "foo"},
|
# "TEST": {"NAME": "foo"},
|
||||||
"USER": "foo",
|
# "USER": "foo",
|
||||||
"CONN_MAX_AGE": 0,
|
# "CONN_MAX_AGE": 0,
|
||||||
"CONN_HEALTH_CHECKS": False,
|
# "CONN_HEALTH_CHECKS": False,
|
||||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||||
}
|
# }
|
||||||
},
|
# },
|
||||||
)
|
# )
|
||||||
|
@ -199,7 +199,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
|||||||
chunk_size = len(ops)
|
chunk_size = len(ops)
|
||||||
if len(ops) < 1:
|
if len(ops) < 1:
|
||||||
return
|
return
|
||||||
for chunk in batched(ops, chunk_size):
|
for chunk in batched(ops, chunk_size, strict=False):
|
||||||
req = PatchRequest(Operations=list(chunk))
|
req = PatchRequest(Operations=list(chunk))
|
||||||
self._request(
|
self._request(
|
||||||
"PATCH",
|
"PATCH",
|
||||||
|
8
go.mod
8
go.mod
@ -19,7 +19,7 @@ require (
|
|||||||
github.com/jellydator/ttlcache/v3 v3.3.0
|
github.com/jellydator/ttlcache/v3 v3.3.0
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.8.0
|
github.com/pires/go-proxyproto v0.8.1
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/redis/go-redis/v9 v9.8.0
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
github.com/sethvargo/go-envconfig v1.3.0
|
github.com/sethvargo/go-envconfig v1.3.0
|
||||||
@ -29,8 +29,8 @@ require (
|
|||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2025040.1
|
goauthentik.io/api/v3 v3.2025040.1
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.29.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.13.0
|
golang.org/x/sync v0.14.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||||
)
|
)
|
||||||
@ -75,7 +75,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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/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 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||||
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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-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-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-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-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-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-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.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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-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.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.14.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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.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.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.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -56,6 +56,7 @@ EXPOSE 3389 6636 9300
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/ldap"]
|
ENTRYPOINT ["/ldap"]
|
||||||
|
@ -97,6 +97,7 @@ elif [[ "$1" == "test-all" ]]; then
|
|||||||
elif [[ "$1" == "healthcheck" ]]; then
|
elif [[ "$1" == "healthcheck" ]]; then
|
||||||
run_authentik healthcheck $(cat $MODE_FILE)
|
run_authentik healthcheck $(cat $MODE_FILE)
|
||||||
elif [[ "$1" == "dump_config" ]]; then
|
elif [[ "$1" == "dump_config" ]]; then
|
||||||
|
shift
|
||||||
exec python -m authentik.lib.config $@
|
exec python -m authentik.lib.config $@
|
||||||
elif [[ "$1" == "debug" ]]; then
|
elif [[ "$1" == "debug" ]]; then
|
||||||
exec sleep infinity
|
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",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1013.0",
|
"aws-cdk": "^2.1014.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -17,9 +17,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk": {
|
"node_modules/aws-cdk": {
|
||||||
"version": "2.1013.0",
|
"version": "2.1014.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1013.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1014.0.tgz",
|
||||||
"integrity": "sha512-cbq4cOoEIZueMWenGgfI4RujS+AQ9GaMCTlW/3CnvEIhMD8j/tgZx7PTtgMuvwYrRoEeb/wTxgLPgUd5FhsoHA==",
|
"integrity": "sha512-es101rtRAClix9BncNL54iW90MiOyRv4iCC5tv/firGDnidS6pPinuK0IIFt0RO6w0+3heRxWBXg8HY+f9877w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1013.0",
|
"aws-cdk": "^2.1014.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
BIN
locale/pt/LC_MESSAGES/django.mo
Normal file
BIN
locale/pt/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
3924
locale/pt/LC_MESSAGES/django.po
Normal file
3924
locale/pt/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -76,6 +76,7 @@ EXPOSE 9000 9300 9443
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/proxy"]
|
ENTRYPOINT ["/proxy"]
|
||||||
|
198
pyproject.toml
198
pyproject.toml
@ -3,102 +3,114 @@ name = "authentik"
|
|||||||
version = "2025.4.0"
|
version = "2025.4.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||||
requires-python = "==3.12.*"
|
requires-python = "==3.13.*"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2-cffi",
|
"argon2-cffi==23.1.0",
|
||||||
"celery",
|
"celery==5.5.2",
|
||||||
"channels",
|
"channels==4.2.2",
|
||||||
"channels-redis",
|
"channels-redis==4.2.1",
|
||||||
"cryptography",
|
"cryptography==44.0.3",
|
||||||
"dacite",
|
"dacite==1.9.2",
|
||||||
"deepmerge",
|
"deepmerge==2.0",
|
||||||
"defusedxml",
|
"defusedxml==0.7.1",
|
||||||
"django",
|
"django==5.1.9",
|
||||||
"django-countries",
|
"django-countries==7.6.1",
|
||||||
"django-cte",
|
"django-cte==1.3.3",
|
||||||
"django-filter",
|
"django-filter==25.1",
|
||||||
"django-guardian",
|
"django-guardian<3.0.0",
|
||||||
"django-model-utils",
|
"django-model-utils==5.0.0",
|
||||||
"django-pglock",
|
"django-pglock==1.7.1",
|
||||||
"django-prometheus",
|
"django-prometheus==2.3.1",
|
||||||
"django-redis",
|
"django-redis==5.4.0",
|
||||||
"django-storages[s3]",
|
"django-storages[s3]==1.14.6",
|
||||||
"django-tenants",
|
"django-tenants==3.7.0",
|
||||||
"djangorestframework",
|
"djangorestframework==3.16.0",
|
||||||
"djangorestframework-guardian",
|
"djangorestframework-guardian==0.3.0",
|
||||||
"docker",
|
"docker==7.1.0",
|
||||||
"drf-orjson-renderer",
|
"drf-orjson-renderer==1.7.3",
|
||||||
"drf-spectacular",
|
"drf-spectacular==0.28.0",
|
||||||
"dumb-init",
|
"dumb-init==1.2.5.post1",
|
||||||
"duo-client",
|
"duo-client==5.5.0",
|
||||||
"fido2",
|
"fido2==1.2.0",
|
||||||
"flower",
|
"flower==2.0.1",
|
||||||
"geoip2",
|
"geoip2==5.1.0",
|
||||||
"geopy",
|
"geopy==2.4.1",
|
||||||
"google-api-python-client",
|
"google-api-python-client==2.169.0",
|
||||||
"gssapi",
|
"gssapi==1.9.0",
|
||||||
"gunicorn",
|
"gunicorn==23.0.0",
|
||||||
"jsonpatch",
|
"jsonpatch==1.33",
|
||||||
"jwcrypto",
|
"jwcrypto==1.5.6",
|
||||||
"kubernetes",
|
"kubernetes==32.0.1",
|
||||||
"ldap3",
|
"ldap3==2.9.1",
|
||||||
"lxml",
|
"lxml==5.4.0",
|
||||||
"msgraph-sdk",
|
"msgraph-sdk==1.30.0",
|
||||||
"opencontainers",
|
"opencontainers==0.0.14",
|
||||||
"packaging",
|
"packaging==25.0",
|
||||||
"paramiko",
|
"paramiko==3.5.1",
|
||||||
"psycopg[c, pool]",
|
"psycopg[c,pool]==3.2.9",
|
||||||
"pydantic",
|
"pydantic==2.11.4",
|
||||||
"pydantic-scim",
|
"pydantic-scim==0.0.8",
|
||||||
"pyjwt",
|
"pyjwt==2.10.1",
|
||||||
"pyrad",
|
"pyrad==2.4",
|
||||||
"python-kadmin-rs ==0.6.0",
|
"python-kadmin-rs==0.6.0",
|
||||||
"pyyaml",
|
"pyyaml==6.0.2",
|
||||||
"requests-oauthlib",
|
"requests-oauthlib==2.0.0",
|
||||||
"scim2-filter-parser",
|
"scim2-filter-parser==0.7.0",
|
||||||
"sentry-sdk",
|
"sentry-sdk==2.28.0",
|
||||||
"service_identity",
|
"service-identity==24.2.0",
|
||||||
"setproctitle",
|
"setproctitle==1.3.6",
|
||||||
"structlog",
|
"structlog==25.3.0",
|
||||||
"swagger-spec-validator",
|
"swagger-spec-validator==3.0.4",
|
||||||
"tenant-schemas-celery",
|
"tenant-schemas-celery==4.0.1",
|
||||||
"twilio",
|
"twilio==9.6.1",
|
||||||
"ua-parser",
|
"ua-parser==1.0.1",
|
||||||
"unidecode",
|
"unidecode==1.4.0",
|
||||||
"urllib3 <3",
|
"urllib3<3",
|
||||||
"uvicorn[standard]",
|
"uvicorn[standard]==0.34.2",
|
||||||
"watchdog",
|
"watchdog==6.0.0",
|
||||||
"webauthn",
|
"webauthn==2.5.2",
|
||||||
"wsproto",
|
"wsproto==1.2.0",
|
||||||
"xmlsec <= 1.3.14",
|
"xmlsec==1.3.15",
|
||||||
"zxcvbn",
|
"zxcvbn==4.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"aws-cdk-lib",
|
"aws-cdk-lib==2.188.0",
|
||||||
"bandit",
|
"bandit==1.8.3",
|
||||||
"black",
|
"black==25.1.0",
|
||||||
"bump2version",
|
"bump2version==1.0.1",
|
||||||
"channels[daphne]",
|
"channels[daphne]==4.2.2",
|
||||||
"codespell",
|
"codespell==2.4.1",
|
||||||
"colorama",
|
"colorama==0.4.6",
|
||||||
"constructs",
|
"constructs==10.4.2",
|
||||||
"coverage[toml]",
|
"coverage[toml]==7.8.0",
|
||||||
"debugpy",
|
"debugpy==1.8.14",
|
||||||
"drf-jsonschema-serializer",
|
"drf-jsonschema-serializer==3.0.0",
|
||||||
"freezegun",
|
"freezegun==1.5.1",
|
||||||
"importlib-metadata",
|
"importlib-metadata==8.6.1",
|
||||||
"k5test",
|
"k5test==0.10.4",
|
||||||
"pdoc",
|
"pdoc==15.0.3",
|
||||||
"pytest",
|
"pytest==8.3.5",
|
||||||
"pytest-django",
|
"pytest-django==4.11.1",
|
||||||
"pytest-github-actions-annotate-failures",
|
"pytest-github-actions-annotate-failures==0.3.0",
|
||||||
"pytest-randomly",
|
"pytest-randomly==3.16.0",
|
||||||
"pytest-timeout",
|
"pytest-timeout==2.4.0",
|
||||||
"requests-mock",
|
"requests-mock==1.12.1",
|
||||||
"ruff",
|
"ruff==0.11.9",
|
||||||
"selenium",
|
"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",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
@ -143,12 +155,12 @@ ignore-words = ".github/codespell-words.txt"
|
|||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = ['py312']
|
target-version = ['py313']
|
||||||
exclude = 'node_modules'
|
exclude = 'node_modules'
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = "py312"
|
target-version = "py313"
|
||||||
exclude = ["**/migrations/**", "**/node_modules/**"]
|
exclude = ["**/migrations/**", "**/node_modules/**"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
|
@ -56,6 +56,7 @@ HEALTHCHECK --interval=5s --retries=20 --start-period=3s CMD [ "/rac", "healthch
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/rac"]
|
ENTRYPOINT ["/rac"]
|
||||||
|
@ -56,6 +56,7 @@ EXPOSE 1812/udp 9300
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/radius"]
|
ENTRYPOINT ["/radius"]
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
services:
|
services:
|
||||||
chrome:
|
chrome:
|
||||||
image: docker.io/selenium/standalone-chrome:122.0
|
image: docker.io/selenium/standalone-chrome:136.0
|
||||||
volumes:
|
volumes:
|
||||||
- /dev/shm:/dev/shm
|
- /dev/shm:/dev/shm
|
||||||
network_mode: host
|
network_mode: host
|
||||||
restart: always
|
restart: always
|
||||||
mailpit:
|
mailpit:
|
||||||
image: docker.io/axllent/mailpit:v1.6.5
|
image: docker.io/axllent/mailpit:v1.24.2
|
||||||
ports:
|
ports:
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
|
51
tests/e2e/test_flows_login_sfe.py
Normal file
51
tests/e2e/test_flows_login_sfe.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""test default login (using SFE interface) flow"""
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
from authentik.blueprints.tests import apply_blueprint
|
||||||
|
from tests.e2e.utils import SeleniumTestCase, retry
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlowsLoginSFE(SeleniumTestCase):
|
||||||
|
"""test default login flow"""
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
"""Do entire login flow adjusted for SFE"""
|
||||||
|
flow_executor = self.driver.find_element(By.ID, "flow-sfe-container")
|
||||||
|
identification_stage = flow_executor.find_element(By.ID, "ident-form")
|
||||||
|
|
||||||
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").click()
|
||||||
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
|
||||||
|
self.user.username
|
||||||
|
)
|
||||||
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
|
||||||
|
Keys.ENTER
|
||||||
|
)
|
||||||
|
|
||||||
|
password_stage = flow_executor.find_element(By.ID, "password-form")
|
||||||
|
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
|
||||||
|
self.user.username
|
||||||
|
)
|
||||||
|
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(Keys.ENTER)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
@apply_blueprint(
|
||||||
|
"default/flow-default-authentication-flow.yaml",
|
||||||
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
|
def test_login(self):
|
||||||
|
"""test default login flow"""
|
||||||
|
self.driver.get(
|
||||||
|
self.url(
|
||||||
|
"authentik_core:if-flow",
|
||||||
|
flow_slug="default-authentication-flow",
|
||||||
|
query={"sfe": True},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.login()
|
||||||
|
self.wait_for_url(self.if_user_url("/library"))
|
||||||
|
self.assert_user(self.user)
|
@ -26,6 +26,7 @@ from selenium import webdriver
|
|||||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
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.webdriver import WebDriver
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
from selenium.webdriver.remote.webelement import WebElement
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
from selenium.webdriver.support.wait import WebDriverWait
|
||||||
@ -197,7 +198,12 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
super().tearDown()
|
super().tearDown()
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::group::Browser logs")
|
print("::group::Browser logs")
|
||||||
for line in self.driver.get_log("browser"):
|
# Very verbose way to get browser logs
|
||||||
|
# https://github.com/SeleniumHQ/selenium/pull/15641
|
||||||
|
# for some reason this removes the `get_log` API from Remote Webdriver
|
||||||
|
# and only keeps it on the local Chrome web driver, even when using
|
||||||
|
# a remote chrome driver...? (nvm the fact this was released as a minor version)
|
||||||
|
for line in self.driver.execute(Command.GET_LOG, {"type": "browser"})["value"]:
|
||||||
print(line["message"])
|
print(line["message"])
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::endgroup::")
|
print("::endgroup::")
|
||||||
@ -235,7 +241,7 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
return element
|
return element
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
"""Do entire login flow and check user afterwards"""
|
"""Do entire login flow"""
|
||||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||||
identification_stage = self.get_shadow_root("ak-stage-identification", 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,7 +7,6 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "@goauthentik/web",
|
"name": "@goauthentik/web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
".",
|
".",
|
||||||
@ -9472,9 +9471,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001667",
|
"version": "1.0.30001716",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz",
|
||||||
"integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
|
"integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -9489,7 +9488,8 @@
|
|||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/ccount": {
|
"node_modules/ccount": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
"lint:precommit": "wireit",
|
"lint:precommit": "wireit",
|
||||||
"lint:types": "wireit",
|
"lint:types": "wireit",
|
||||||
"lit-analyse": "wireit",
|
"lit-analyse": "wireit",
|
||||||
"postinstall": "bash scripts/patch-spotlight.sh",
|
|
||||||
"precommit": "wireit",
|
"precommit": "wireit",
|
||||||
"prettier": "wireit",
|
"prettier": "wireit",
|
||||||
"prettier-check": "wireit",
|
"prettier-check": "wireit",
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* @import { Message as ESBuildMessage } from "esbuild";
|
* @import { Message as ESBuildMessage } from "esbuild";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const logPrefix = "👷 [ESBuild]";
|
const logPrefix = "authentik/dev/web: ";
|
||||||
const log = console.debug.bind(console, logPrefix);
|
const log = console.debug.bind(console, logPrefix);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,7 +76,7 @@ export class ESBuildObserver extends EventSource {
|
|||||||
*/
|
*/
|
||||||
#startListener = () => {
|
#startListener = () => {
|
||||||
this.#trackActivity();
|
this.#trackActivity();
|
||||||
log("⏰ Build started...");
|
log("⏰ Build started...");
|
||||||
};
|
};
|
||||||
|
|
||||||
#internalErrorListener = () => {
|
#internalErrorListener = () => {
|
||||||
@ -86,7 +86,7 @@ export class ESBuildObserver extends EventSource {
|
|||||||
clearTimeout(this.#keepAliveInterval);
|
clearTimeout(this.#keepAliveInterval);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
log("⛔️ Closing connection");
|
log("⛔️ Closing connection");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,13 +126,13 @@ export class ESBuildObserver extends EventSource {
|
|||||||
this.#trackActivity();
|
this.#trackActivity();
|
||||||
|
|
||||||
if (!this.online) {
|
if (!this.online) {
|
||||||
log("🚫 Build finished while offline.");
|
log("🚫 Build finished while offline.");
|
||||||
this.deferredReload = true;
|
this.deferredReload = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("🛎️ Build completed! Reloading...");
|
log("🛎️ Build completed! Reloading...");
|
||||||
|
|
||||||
// We use an animation frame to keep the reload from happening before the
|
// We use an animation frame to keep the reload from happening before the
|
||||||
// event loop has a chance to process the message.
|
// event loop has a chance to process the message.
|
||||||
@ -189,13 +189,13 @@ export class ESBuildObserver extends EventSource {
|
|||||||
|
|
||||||
if (!this.deferredReload) return;
|
if (!this.deferredReload) return;
|
||||||
|
|
||||||
log("🛎️ Reloading after offline build...");
|
log("🛎️ Reloading after offline build...");
|
||||||
this.deferredReload = false;
|
this.deferredReload = false;
|
||||||
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
log("🛎️ Listening for build changes...");
|
log("🛎️ Listening for build changes...");
|
||||||
|
|
||||||
this.#keepAliveInterval = setInterval(() => {
|
this.#keepAliveInterval = setInterval(() => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -203,7 +203,7 @@ export class ESBuildObserver extends EventSource {
|
|||||||
if (now - this.lastUpdatedAt < 10_000) return;
|
if (now - this.lastUpdatedAt < 10_000) return;
|
||||||
|
|
||||||
this.alive = false;
|
this.alive = false;
|
||||||
log("👋 Waiting for build to start...");
|
log("👋 Waiting for build to start...");
|
||||||
}, 15_000);
|
}, 15_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,16 @@ class SimpleFlowExecutor {
|
|||||||
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
|
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() {
|
start() {
|
||||||
|
this.loading();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: this.apiURL,
|
url: this.apiURL,
|
||||||
@ -201,6 +210,9 @@ class PasswordStage extends Stage<PasswordChallenge> {
|
|||||||
<form id="password-form">
|
<form id="password-form">
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
<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>
|
<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">
|
<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">
|
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
||||||
${this.renderInputError("password")}
|
${this.renderInputError("password")}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
TARGET="./node_modules/@spotlightjs/overlay/dist/index-"[0-9a-f]*.js
|
|
||||||
|
|
||||||
if [[ $(grep -L "QX2" "$TARGET" > /dev/null 2> /dev/null) ]]; then
|
|
||||||
patch --forward -V none --no-backup-if-mismatch -p0 $TARGET <<EOF
|
|
||||||
|
|
||||||
TARGET=$(find "./node_modules/@spotlightjs/overlay/dist/" -name "index-[0-9a-f]*.js");
|
|
||||||
|
|
||||||
if ! grep -GL 'QX2 = ' "$TARGET" > /dev/null ; then
|
|
||||||
patch --forward --no-backup-if-mismatch -p0 "$TARGET" <<EOF
|
|
||||||
>>>>>>> main
|
|
||||||
--- a/index-5682ce90.js 2024-06-13 16:19:28
|
|
||||||
+++ b/index-5682ce90.js 2024-06-13 16:20:23
|
|
||||||
@@ -4958,11 +4958,10 @@
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-const q2 = w.lazy(() => import("./main-3257b7fc.js").then((n) => n.m));
|
|
||||||
+const q2 = w.lazy(() => import("./main-3257b7fc.js").then((n) => n.m)), QX2 = () => {};
|
|
||||||
function Gp({
|
|
||||||
data: n,
|
|
||||||
- onUpdateData: a = () => {
|
|
||||||
- },
|
|
||||||
+ onUpdateData: a = QX2,
|
|
||||||
editingEnabled: s = !1,
|
|
||||||
clipboardEnabled: o = !1,
|
|
||||||
displayDataTypes: c = !1,
|
|
||||||
EOF
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "spotlight overlay.js patch already applied"
|
|
||||||
fi
|
|
@ -131,9 +131,9 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
//#region Lifecycle
|
//#region Lifecycle
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
configureSentry(true);
|
||||||
super();
|
super();
|
||||||
this.ws = new WebsocketClient();
|
this.ws = new WebsocketClient();
|
||||||
|
|
||||||
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
||||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||||
}
|
}
|
||||||
@ -167,7 +167,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(): Promise<void> {
|
||||||
configureSentry(true);
|
|
||||||
this.user = await me();
|
this.user = await me();
|
||||||
|
|
||||||
const canAccessAdmin =
|
const canAccessAdmin =
|
||||||
|
@ -113,8 +113,7 @@ export class ApplicationViewPage extends AKElement {
|
|||||||
|
|
||||||
renderApp(): TemplateResult {
|
renderApp(): TemplateResult {
|
||||||
if (!this.application) {
|
if (!this.application) {
|
||||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||||
</ak-empty-state>`;
|
|
||||||
}
|
}
|
||||||
return html`<ak-tabs>
|
return html`<ak-tabs>
|
||||||
${this.missingOutpost
|
${this.missingOutpost
|
||||||
|
@ -42,7 +42,7 @@ export class ProviderViewPage extends AKElement {
|
|||||||
|
|
||||||
renderProvider(): TemplateResult {
|
renderProvider(): TemplateResult {
|
||||||
if (!this.provider) {
|
if (!this.provider) {
|
||||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
switch (this.provider?.component) {
|
switch (this.provider?.component) {
|
||||||
case "ak-provider-saml-form":
|
case "ak-provider-saml-form":
|
||||||
|
@ -432,7 +432,7 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
${this.preview
|
${this.preview
|
||||||
? html`<pre>${JSON.stringify(this.preview?.preview, null, 4)}</pre>`
|
? html`<pre>${JSON.stringify(this.preview?.preview, null, 4)}</pre>`
|
||||||
: html` <ak-empty-state ?loading=${true}></ak-empty-state> `}
|
: html` <ak-empty-state loading></ak-empty-state> `}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -502,7 +502,7 @@ export class SAMLProviderViewPage extends AKElement {
|
|||||||
|
|
||||||
renderTabPreview(): TemplateResult {
|
renderTabPreview(): TemplateResult {
|
||||||
if (!this.preview) {
|
if (!this.preview) {
|
||||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html` <div
|
return html` <div
|
||||||
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
|
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 {
|
renderSource(): TemplateResult {
|
||||||
if (!this.source) {
|
if (!this.source) {
|
||||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
switch (this.source?.component) {
|
switch (this.source?.component) {
|
||||||
case "ak-source-kerberos-form":
|
case "ak-source-kerberos-form":
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from "@goauthentik/common/api/middleware";
|
} from "@goauthentik/common/api/middleware";
|
||||||
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
|
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
|
||||||
import { globalAK } from "@goauthentik/common/global";
|
import { globalAK } from "@goauthentik/common/global";
|
||||||
|
import { SentryMiddleware } from "@goauthentik/common/sentry";
|
||||||
|
|
||||||
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
||||||
|
|
||||||
@ -66,21 +67,13 @@ export function brand(): Promise<CurrentBrand> {
|
|||||||
return globalBrandPromise;
|
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({
|
export const DEFAULT_CONFIG = new Configuration({
|
||||||
basePath: `${globalAK().api.base}api/v3`,
|
basePath: `${globalAK().api.base}api/v3`,
|
||||||
headers: {
|
|
||||||
"sentry-trace": getMetaContent("sentry-trace"),
|
|
||||||
},
|
|
||||||
middleware: [
|
middleware: [
|
||||||
new CSRFMiddleware(),
|
new CSRFMiddleware(),
|
||||||
new EventMiddleware(),
|
new EventMiddleware(),
|
||||||
new LoggingMiddleware(globalAK().brand),
|
new LoggingMiddleware(globalAK().brand),
|
||||||
|
new SentryMiddleware(),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { config } from "@goauthentik/common/api/config";
|
|
||||||
import { VERSION } from "@goauthentik/common/constants";
|
import { VERSION } from "@goauthentik/common/constants";
|
||||||
|
import { globalAK } from "@goauthentik/common/global";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||||
import {
|
import {
|
||||||
@ -10,8 +10,16 @@ import {
|
|||||||
setTag,
|
setTag,
|
||||||
setUser,
|
setUser,
|
||||||
} from "@sentry/browser";
|
} from "@sentry/browser";
|
||||||
|
import { getTraceData } from "@sentry/core";
|
||||||
|
import * as Spotlight from "@spotlightjs/spotlight";
|
||||||
|
|
||||||
import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api";
|
import {
|
||||||
|
CapabilitiesEnum,
|
||||||
|
FetchParams,
|
||||||
|
Middleware,
|
||||||
|
RequestContext,
|
||||||
|
ResponseError,
|
||||||
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic error that can be thrown without triggering Sentry's reporting.
|
* A generic error that can be thrown without triggering Sentry's reporting.
|
||||||
@ -21,69 +29,94 @@ export class SentryIgnoredError extends Error {}
|
|||||||
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
||||||
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
||||||
|
|
||||||
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
let _sentryConfigured = false;
|
||||||
const cfg = await config();
|
|
||||||
|
|
||||||
if (cfg.errorReporting.enabled) {
|
export function configureSentry(canDoPpi = false) {
|
||||||
init({
|
const cfg = globalAK().config;
|
||||||
dsn: cfg.errorReporting.sentryDsn,
|
const debug = cfg.capabilities.includes(CapabilitiesEnum.CanDebug);
|
||||||
ignoreErrors: [
|
if (!cfg.errorReporting.enabled && !debug) {
|
||||||
/network/gi,
|
return cfg;
|
||||||
/fetch/gi,
|
}
|
||||||
/module/gi,
|
init({
|
||||||
// Error on edge on ios,
|
dsn: cfg.errorReporting.sentryDsn,
|
||||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
ignoreErrors: [
|
||||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
/network/gi,
|
||||||
// Seems to be an issue in Safari and Firefox
|
/fetch/gi,
|
||||||
/MutationObserver.observe/gi,
|
/module/gi,
|
||||||
/NS_ERROR_FAILURE/gi,
|
// Error on edge on ios,
|
||||||
],
|
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||||
release: `authentik@${VERSION}`,
|
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||||
|
// Seems to be an issue in Safari and Firefox
|
||||||
|
/MutationObserver.observe/gi,
|
||||||
|
/NS_ERROR_FAILURE/gi,
|
||||||
|
],
|
||||||
|
release: `authentik@${VERSION}`,
|
||||||
|
integrations: [
|
||||||
|
browserTracingIntegration({
|
||||||
|
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
|
||||||
|
instrumentNavigation: false,
|
||||||
|
instrumentPageLoad: false,
|
||||||
|
traceFetch: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
tracePropagationTargets: [window.location.origin],
|
||||||
|
tracesSampleRate: debug ? 1.0 : cfg.errorReporting.tracesSampleRate,
|
||||||
|
environment: cfg.errorReporting.environment,
|
||||||
|
beforeSend: (
|
||||||
|
event: ErrorEvent,
|
||||||
|
hint: EventHint,
|
||||||
|
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
|
||||||
|
if (!hint) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
if (hint.originalException instanceof SentryIgnoredError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
hint.originalException instanceof ResponseError ||
|
||||||
|
hint.originalException instanceof DOMException
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
||||||
|
if (window.location.pathname.includes("if/")) {
|
||||||
|
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
||||||
|
}
|
||||||
|
if (debug) {
|
||||||
|
Spotlight.init({
|
||||||
|
injectImmediately: true,
|
||||||
integrations: [
|
integrations: [
|
||||||
browserTracingIntegration({
|
Spotlight.sentry({
|
||||||
shouldCreateSpanForRequest: (url: string) => {
|
injectIntoSDK: true,
|
||||||
return url.startsWith(window.location.host);
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
tracesSampleRate: cfg.errorReporting.tracesSampleRate,
|
|
||||||
environment: cfg.errorReporting.environment,
|
|
||||||
beforeSend: (
|
|
||||||
event: ErrorEvent,
|
|
||||||
hint: EventHint,
|
|
||||||
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
|
|
||||||
if (!hint) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
if (hint.originalException instanceof SentryIgnoredError) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
hint.originalException instanceof ResponseError ||
|
|
||||||
hint.originalException instanceof DOMException
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return event;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
console.debug("authentik/config: Enabled Sentry Spotlight");
|
||||||
if (window.location.pathname.includes("if/")) {
|
}
|
||||||
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
if (cfg.errorReporting.sendPii && canDoPpi) {
|
||||||
}
|
me().then((user) => {
|
||||||
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
setUser({ email: user.user.email });
|
||||||
const Spotlight = await import("@spotlightjs/spotlight");
|
console.debug("authentik/config: Sentry with PII enabled.");
|
||||||
|
});
|
||||||
Spotlight.init({ injectImmediately: true });
|
} else {
|
||||||
}
|
console.debug("authentik/config: Sentry enabled.");
|
||||||
if (cfg.errorReporting.sendPii && canDoPpi) {
|
}
|
||||||
me().then((user) => {
|
_sentryConfigured = true;
|
||||||
setUser({ email: user.user.email });
|
}
|
||||||
console.debug("authentik/config: Sentry with PII enabled.");
|
|
||||||
});
|
export class SentryMiddleware implements Middleware {
|
||||||
} else {
|
pre?(context: RequestContext): Promise<FetchParams | void> {
|
||||||
console.debug("authentik/config: Sentry enabled.");
|
if (!_sentryConfigured) {
|
||||||
}
|
return Promise.resolve(context);
|
||||||
|
}
|
||||||
|
const traceData = getTraceData();
|
||||||
|
// @ts-ignore
|
||||||
|
context.init.headers["baggage"] = traceData["baggage"];
|
||||||
|
// @ts-ignore
|
||||||
|
context.init.headers["sentry-trace"] = traceData["sentry-trace"];
|
||||||
|
return Promise.resolve(context);
|
||||||
}
|
}
|
||||||
return cfg;
|
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ export class Diagram extends AKElement {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!this.diagram) {
|
if (!this.diagram) {
|
||||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html`${until(
|
return html`${until(
|
||||||
mermaid.render("graph", this.diagram).then((r) => {
|
mermaid.render("graph", this.diagram).then((r) => {
|
||||||
|
@ -23,9 +23,20 @@ const configContext = Symbol("configContext");
|
|||||||
const modalController = Symbol("modalController");
|
const modalController = Symbol("modalController");
|
||||||
const versionContext = Symbol("versionContext");
|
const versionContext = Symbol("versionContext");
|
||||||
|
|
||||||
export abstract class Interface extends AKElement implements ThemedElement {
|
export abstract class LightInterface extends AKElement implements ThemedElement {
|
||||||
protected static readonly PFBaseStyleSheet = createStyleSheetUnsafe(PFBase);
|
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;
|
[configContext]: ConfigContextController;
|
||||||
|
|
||||||
[modalController]: ModalOrchestrationController;
|
[modalController]: ModalOrchestrationController;
|
||||||
@ -38,12 +49,6 @@ export abstract class Interface extends AKElement implements ThemedElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
const styleParent = resolveStyleSheetParent(document);
|
|
||||||
|
|
||||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
|
||||||
|
|
||||||
appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
|
|
||||||
|
|
||||||
this.addController(new BrandContextController(this));
|
this.addController(new BrandContextController(this));
|
||||||
this[configContext] = new ConfigContextController(this);
|
this[configContext] = new ConfigContextController(this);
|
||||||
this[modalController] = new ModalOrchestrationController(this);
|
this[modalController] = new ModalOrchestrationController(this);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AuthenticatedInterface, Interface } from "./Interface";
|
import { AuthenticatedInterface, Interface, LightInterface } from "./Interface";
|
||||||
|
|
||||||
export { Interface, AuthenticatedInterface };
|
export { Interface, AuthenticatedInterface, LightInterface };
|
||||||
export default Interface;
|
export default Interface;
|
||||||
|
@ -230,9 +230,7 @@ export abstract class AKChart<T> extends AKElement {
|
|||||||
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
`
|
`
|
||||||
: html`${this.chart
|
: html`${this.chart ? html`` : html`<ak-empty-state loading></ak-empty-state>`}`}
|
||||||
? html``
|
|
||||||
: html`<ak-empty-state ?loading="${true}"></ak-empty-state>`}`}
|
|
||||||
${this.centerText ? html` <span>${this.centerText}</span> ` : html``}
|
${this.centerText ? html` <span>${this.centerText}</span> ` : html``}
|
||||||
<canvas style="${this.chart === undefined ? "display: none;" : ""}"></canvas>
|
<canvas style="${this.chart === undefined ? "display: none;" : ""}"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,7 +71,7 @@ export abstract class ModelForm<T, PKT extends string | number> extends Form<T>
|
|||||||
|
|
||||||
renderVisible(): TemplateResult {
|
renderVisible(): TemplateResult {
|
||||||
if ((this._instancePk && !this.instance) || !this._initialDataLoad) {
|
if ((this._instancePk && !this.instance) || !this._initialDataLoad) {
|
||||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return super.renderVisible();
|
return super.renderVisible();
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ export class Route {
|
|||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
return html`${until(
|
return html`${until(
|
||||||
this.callback(args),
|
this.callback(args),
|
||||||
html`<ak-empty-state ?loading=${true}></ak-empty-state>`,
|
html`<ak-empty-state loading></ak-empty-state>`,
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
if (this.element) {
|
if (this.element) {
|
||||||
|
@ -6,19 +6,35 @@ import { TemplateResult } from "lit";
|
|||||||
export class RouteMatch {
|
export class RouteMatch {
|
||||||
route: Route;
|
route: Route;
|
||||||
arguments: { [key: string]: string };
|
arguments: { [key: string]: string };
|
||||||
fullUrl?: string;
|
fullURL: string;
|
||||||
|
|
||||||
constructor(route: Route) {
|
constructor(route: Route, fullUrl: string) {
|
||||||
this.route = route;
|
this.route = route;
|
||||||
this.arguments = {};
|
this.arguments = {};
|
||||||
|
this.fullURL = fullUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return this.route.render(this.arguments);
|
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 {
|
toString(): string {
|
||||||
return `<RouteMatch url=${this.fullUrl} route=${this.route} arguments=${JSON.stringify(
|
return `<RouteMatch url=${this.sanitizedURL()} route=${this.route} arguments=${JSON.stringify(
|
||||||
this.arguments,
|
this.arguments,
|
||||||
)}>`;
|
)}>`;
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,15 @@ import { AKElement } from "@goauthentik/elements/Base";
|
|||||||
import { Route } from "@goauthentik/elements/router/Route";
|
import { Route } from "@goauthentik/elements/router/Route";
|
||||||
import { RouteMatch } from "@goauthentik/elements/router/RouteMatch";
|
import { RouteMatch } from "@goauthentik/elements/router/RouteMatch";
|
||||||
import "@goauthentik/elements/router/Router404";
|
import "@goauthentik/elements/router/Router404";
|
||||||
|
import {
|
||||||
|
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
|
||||||
|
getClient,
|
||||||
|
startBrowserTracingNavigationSpan,
|
||||||
|
startBrowserTracingPageLoadSpan,
|
||||||
|
} from "@sentry/browser";
|
||||||
|
import { Client, Span } from "@sentry/types";
|
||||||
|
|
||||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
import { CSSResult, PropertyValues, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
// Poliyfill for hashchange.newURL,
|
// Poliyfill for hashchange.newURL,
|
||||||
@ -53,6 +60,9 @@ export class RouterOutlet extends AKElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
routes: Route[] = [];
|
routes: Route[] = [];
|
||||||
|
|
||||||
|
private sentryClient?: Client;
|
||||||
|
private pageLoadSpan?: Span;
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
@ -69,6 +79,15 @@ export class RouterOutlet extends AKElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
window.addEventListener("hashchange", (ev: HashChangeEvent) => this.navigate(ev));
|
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 {
|
firstUpdated(): void {
|
||||||
@ -92,9 +111,8 @@ export class RouterOutlet extends AKElement {
|
|||||||
this.routes.some((route) => {
|
this.routes.some((route) => {
|
||||||
const match = route.url.exec(activeUrl);
|
const match = route.url.exec(activeUrl);
|
||||||
if (match !== null) {
|
if (match !== null) {
|
||||||
matchedRoute = new RouteMatch(route);
|
matchedRoute = new RouteMatch(route, activeUrl);
|
||||||
matchedRoute.arguments = match.groups || {};
|
matchedRoute.arguments = match.groups || {};
|
||||||
matchedRoute.fullUrl = activeUrl;
|
|
||||||
console.debug("authentik/router: found match ", matchedRoute);
|
console.debug("authentik/router: found match ", matchedRoute);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -107,13 +125,31 @@ export class RouterOutlet extends AKElement {
|
|||||||
<ak-router-404 url=${activeUrl}></ak-router-404>
|
<ak-router-404 url=${activeUrl}></ak-router-404>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
matchedRoute = new RouteMatch(route);
|
matchedRoute = new RouteMatch(route, activeUrl);
|
||||||
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
|
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
|
||||||
matchedRoute.fullUrl = activeUrl;
|
|
||||||
}
|
}
|
||||||
this.current = matchedRoute;
|
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 {
|
render(): TemplateResult | undefined {
|
||||||
return this.current?.render();
|
return this.current?.render();
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ export class SyncStatusCard extends AKElement {
|
|||||||
|
|
||||||
renderSyncStatus(): TemplateResult {
|
renderSyncStatus(): TemplateResult {
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
if (!this.syncState) {
|
if (!this.syncState) {
|
||||||
return html`${msg("No sync status.")}`;
|
return html`${msg("No sync status.")}`;
|
||||||
|
@ -19,7 +19,7 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render the default loader", async () => {
|
it("should render the default loader", async () => {
|
||||||
render(html`<ak-empty-state ?loading=${true} header=${msg("Loading")}> </ak-empty-state>`);
|
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
|
@ -139,8 +139,7 @@ export class UserSourceSettingsPage extends AKElement {
|
|||||||
})}
|
})}
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
: html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
: html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`}
|
||||||
</ak-empty-state>`}
|
|
||||||
</ul>`;
|
</ul>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,6 +171,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
configureSentry();
|
||||||
super();
|
super();
|
||||||
this.ws = new WebsocketClient();
|
this.ws = new WebsocketClient();
|
||||||
const inspector = new URL(window.location.toString()).searchParams.get("inspector");
|
const inspector = new URL(window.location.toString()).searchParams.get("inspector");
|
||||||
@ -237,7 +238,6 @@ export class FlowExecutor extends Interface implements StageHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(): Promise<void> {
|
||||||
configureSentry();
|
|
||||||
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||||
this.inspectorAvailable = true;
|
this.inspectorAvailable = true;
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,7 @@ export class SessionEnd extends BaseStage<SessionEndChallenge, unknown> {
|
|||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||||
</ak-empty-state>`;
|
|
||||||
}
|
}
|
||||||
return html`<header class="pf-c-login__main-header">
|
return html`<header class="pf-c-login__main-header">
|
||||||
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Interface } from "@goauthentik/elements/Interface";
|
import { LightInterface } from "@goauthentik/elements/Interface";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
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";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
@customElement("ak-loading")
|
@customElement("ak-loading")
|
||||||
export class Loading extends Interface {
|
export class Loading extends LightInterface {
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
@ -25,16 +25,6 @@ export class Loading extends Interface {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
registerContexts(): void {
|
|
||||||
// Stub function to avoid making API requests for things we don't need. The `Interface` base class loads
|
|
||||||
// a bunch of data that is used globally by various things, however this is an interface that is shown
|
|
||||||
// very briefly and we don't need any of that data.
|
|
||||||
}
|
|
||||||
|
|
||||||
async _initCustomCSS(): Promise<void> {
|
|
||||||
// Stub function to avoid fetching custom CSS.
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html` <section
|
return html` <section
|
||||||
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
||||||
|
@ -102,7 +102,7 @@ export class LibraryPage extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loading() {
|
loading() {
|
||||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
running() {
|
running() {
|
||||||
|
@ -281,10 +281,10 @@ export class UserInterface extends AuthenticatedInterface {
|
|||||||
me?: SessionUser;
|
me?: SessionUser;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
configureSentry(true);
|
||||||
super();
|
super();
|
||||||
this.ws = new WebsocketClient();
|
this.ws = new WebsocketClient();
|
||||||
this.fetchConfigurationDetails();
|
this.fetchConfigurationDetails();
|
||||||
configureSentry(true);
|
|
||||||
this.toggleNotificationDrawer = this.toggleNotificationDrawer.bind(this);
|
this.toggleNotificationDrawer = this.toggleNotificationDrawer.bind(this);
|
||||||
this.toggleApiDrawer = this.toggleApiDrawer.bind(this);
|
this.toggleApiDrawer = this.toggleApiDrawer.bind(this);
|
||||||
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
|
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
|
||||||
|
@ -173,8 +173,7 @@ export class UserSettingsFlowExecutor
|
|||||||
level: MessageLevel.success,
|
level: MessageLevel.success,
|
||||||
message: msg("Successfully updated details"),
|
message: msg("Successfully updated details"),
|
||||||
});
|
});
|
||||||
return html`<ak-empty-state ?loading=${true} header=${msg("Loading")}>
|
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||||
</ak-empty-state>`;
|
|
||||||
default:
|
default:
|
||||||
console.debug(
|
console.debug(
|
||||||
`authentik/user/flows: unsupported stage type ${this.challenge.component}`,
|
`authentik/user/flows: unsupported stage type ${this.challenge.component}`,
|
||||||
@ -195,8 +194,7 @@ export class UserSettingsFlowExecutor
|
|||||||
return html`<p>${msg("No settings flow configured.")}</p> `;
|
return html`<p>${msg("No settings flow configured.")}</p> `;
|
||||||
}
|
}
|
||||||
if (!this.challenge || this.loading) {
|
if (!this.challenge || this.loading) {
|
||||||
return html`<ak-empty-state ?loading=${true} header=${msg("Loading")}>
|
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||||
</ak-empty-state>`;
|
|
||||||
}
|
}
|
||||||
return html` ${this.renderChallenge()} `;
|
return html` ${this.renderChallenge()} `;
|
||||||
}
|
}
|
||||||
|
@ -64,8 +64,7 @@ export class UserSettingsPromptStage extends PromptStage {
|
|||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||||
</ak-empty-state>`;
|
|
||||||
}
|
}
|
||||||
return html`<div class="pf-c-login__main-body">
|
return html`<div class="pf-c-login__main-body">
|
||||||
<form
|
<form
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Willkommen,
|
<target>Willkommen,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
|
|
||||||
@ -9171,6 +9171,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -493,7 +493,7 @@
|
|||||||
<target>General system status</target>
|
<target>General system status</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Welcome,
|
<target>Welcome,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -7694,6 +7694,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Bienvenido,
|
<target>Bienvenido,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
|
|
||||||
@ -9253,6 +9253,9 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Bienvenue,
|
<target>Bienvenue,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
|
|
||||||
@ -9805,6 +9805,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
|||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
<target>Nombre d'anciens mots de passe à vérifier</target>
|
<target>Nombre d'anciens mots de passe à vérifier</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Benvenuto,
|
<target>Benvenuto,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
|
|
||||||
@ -9779,6 +9779,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -597,7 +597,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc381422c585b867f">
|
<trans-unit id="sc381422c585b867f">
|
||||||
@ -9161,6 +9161,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -605,7 +605,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Welkom,
|
<target>Welkom,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
|
|
||||||
@ -9063,6 +9063,9 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Witaj,
|
<target>Witaj,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
|
|
||||||
@ -9488,6 +9488,9 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -603,7 +603,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Ŵēĺćōḿē, <x id="0" equiv-text="${name || ""}"/>.</target>
|
<target>Ŵēĺćōḿē, <x id="0" equiv-text="${name || ""}"/>.</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9496,4 +9496,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
|
</trans-unit>
|
||||||
</body></file></xliff>
|
</body></file></xliff>
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Добро пожаловать,
|
<target>Добро пожаловать,
|
||||||
<x id="0" equiv-text="${name}"/>.</target>
|
<x id="0" equiv-text="${name}"/>.</target>
|
||||||
|
|
||||||
@ -9581,6 +9581,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -602,7 +602,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>Hoş geldiniz, <x id="0" equiv-text="${name || ""}"/>.</target>
|
<target>Hoş geldiniz, <x id="0" equiv-text="${name || ""}"/>.</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9551,6 +9551,9 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -399,7 +399,7 @@
|
|||||||
<source>General system status</source>
|
<source>General system status</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc381422c585b867f">
|
<trans-unit id="sc381422c585b867f">
|
||||||
<source>Quick actions</source>
|
<source>Quick actions</source>
|
||||||
@ -6302,6 +6302,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>欢迎,
|
<target>欢迎,
|
||||||
<x id="0" equiv-text="${name}"/>。</target>
|
<x id="0" equiv-text="${name}"/>。</target>
|
||||||
|
|
||||||
@ -9806,6 +9806,10 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
<target>检查历史密码数量</target>
|
<target>检查历史密码数量</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
|
<target>切换侧边栏</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -485,7 +485,7 @@
|
|||||||
<target>常规系统状态</target>
|
<target>常规系统状态</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>欢迎,
|
<target>欢迎,
|
||||||
<x id="0" equiv-text="${name}"/>。</target>
|
<x id="0" equiv-text="${name}"/>。</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -7394,6 +7394,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
<target>欢迎,
|
<target>欢迎,
|
||||||
<x id="0" equiv-text="${name}"/>。</target>
|
<x id="0" equiv-text="${name}"/>。</target>
|
||||||
|
|
||||||
@ -9806,6 +9806,10 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
<target>检查历史密码数量</target>
|
<target>检查历史密码数量</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
|
<target>切换侧边栏</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -596,7 +596,7 @@
|
|||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6dfd15978586d05f">
|
<trans-unit id="s6dfd15978586d05f">
|
||||||
<source>Welcome, <x id="0" equiv-text="${name || ""}"/>.</source>
|
<source>Welcome, <x id="0" equiv-text="${username || ""}"/>.</source>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc381422c585b867f">
|
<trans-unit id="sc381422c585b867f">
|
||||||
@ -9138,6 +9138,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s79b3fcd40dd63921">
|
<trans-unit id="s79b3fcd40dd63921">
|
||||||
<source>Number of previous passwords to check</source>
|
<source>Number of previous passwords to check</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdd66c5a2e706fb81">
|
||||||
|
<source>Toggle sidebar</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
2
website/.gitignore
vendored
2
website/.gitignore
vendored
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
# Production
|
# Production
|
||||||
/build
|
/build
|
||||||
|
/out
|
||||||
/help
|
/help
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
@ -25,4 +26,5 @@ yarn-error.log*
|
|||||||
|
|
||||||
static/docker-compose.yml
|
static/docker-compose.yml
|
||||||
static/schema.yml
|
static/schema.yml
|
||||||
|
static/releases.gen.json
|
||||||
docs/developer-docs/api/reference/**
|
docs/developer-docs/api/reference/**
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Ignore artifacts:
|
# Ignore artifacts:
|
||||||
build
|
build
|
||||||
|
out
|
||||||
coverage
|
coverage
|
||||||
.docusaurus
|
.docusaurus
|
||||||
node_modules
|
node_modules
|
||||||
|
@ -1 +0,0 @@
|
|||||||
{}
|
|
@ -88,4 +88,4 @@ import Defaultflowlist from "../flow/flow_list/\_defaultflowlist.mdx";
|
|||||||
|
|
||||||
- **Layout**: select how the UI displays the flow when it is executed; with stacked elements, content left or right, and sidebar left or right.
|
- **Layout**: select how the UI displays the flow when it is executed; with stacked elements, content left or right, and sidebar left or right.
|
||||||
|
|
||||||
- **Background**: optionally, select a background image for the UI presentation of the flow.
|
- **Background**: optionally, select a background image for the UI presentation of the flow. This overrides any default background image configured in the [Branding settings](../../../sys-mgmt/brands.md#branding-settings).
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
---
|
---
|
||||||
title: Email Authenticator Setup stage
|
title: Email Authenticator Setup stage
|
||||||
|
authentik_version: "2025.2"
|
||||||
---
|
---
|
||||||
|
|
||||||
<span class="badge badge--version">authentik 2025.2+</span>
|
|
||||||
|
|
||||||
This stage configures an email-based authenticator that sends a one-time code to a user's email address for authentication.
|
This stage configures an email-based authenticator that sends a one-time code to a user's email address for authentication.
|
||||||
|
|
||||||
When a user goes through a flow that includes this stage, they are prompted for their email address (if not already set). The user then receives an email with a one-time code, which they enter into the authentik Login panel.
|
When a user goes through a flow that includes this stage, they are prompted for their email address (if not already set). The user then receives an email with a one-time code, which they enter into the authentik Login panel.
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
title: WebAuthn / Passkeys Authenticator setup stage
|
title: WebAuthn / Passkeys Authenticator setup stage
|
||||||
---
|
---
|
||||||
|
|
||||||
<span class="badge badge--version">authentik 2021.3.1+</span>
|
|
||||||
|
|
||||||
This stage configures a WebAuthn-based Authenticator. This can either be a browser, biometrics or a Security stick like a YubiKey.
|
This stage configures a WebAuthn-based Authenticator. This can either be a browser, biometrics or a Security stick like a YubiKey.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
@ -29,8 +29,8 @@ You can also use custom email templates, to use your own design or layout.
|
|||||||
Starting with authentik 2024.2, it is possible to create `.txt` files with the same name as the `.html` template. If a matching `.txt` file exists, the email sent will be a multipart email with both the text and HTML template.
|
Starting with authentik 2024.2, it is possible to create `.txt` files with the same name as the `.html` template. If a matching `.txt` file exists, the email sent will be a multipart email with both the text and HTML template.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
import Tabs from "@theme/Tabs";
|
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue="docker-compose"
|
defaultValue="docker-compose"
|
||||||
|
@ -57,7 +57,7 @@ To bind a stage to a flow, follow these steps:
|
|||||||
|
|
||||||
## Bind users and groups to a flow's stage binding
|
## Bind users and groups to a flow's stage binding
|
||||||
|
|
||||||
You can use bindings to determine whehther or not a stage is presented to a single user or any users within a group. You do this by binding the user or group to a stage binding within a specific flow. For example, if you have a flow that contains a stage that prompts the user for multi-factor authentication, but you only want certain users to see this stage (and fulfill the MFA prompt), then you would bind the appropriate group (or single user) to the stage binding for that flow.
|
You can use bindings to determine whether or not a stage is presented to a single user or any users within a group. You do this by binding the user or group to a stage binding within a specific flow. For example, if you have a flow that contains a stage that prompts the user for multi-factor authentication, but you only want certain users to see this stage (and fulfill the MFA prompt), then you would bind the appropriate group (or single user) to the stage binding for that flow.
|
||||||
|
|
||||||
To bind a user or a group to a stage binding for a specific flow, follow these steps:
|
To bind a user or a group to a stage binding for a specific flow, follow these steps:
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
title: Caddy
|
title: Caddy
|
||||||
---
|
---
|
||||||
|
|
||||||
import Tabs from "@theme/Tabs";
|
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
|
||||||
import Placeholders from "./__placeholders.md";
|
import Placeholders from "./__placeholders.md";
|
||||||
import CaddyStandalone from "./_caddy_standalone.md";
|
import CaddyStandalone from "./_caddy_standalone.md";
|
||||||
|
|
||||||
|
@ -2,13 +2,12 @@
|
|||||||
title: Envoy
|
title: Envoy
|
||||||
---
|
---
|
||||||
|
|
||||||
import Tabs from "@theme/Tabs";
|
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
|
||||||
import Placeholders from "./__placeholders.md";
|
import Placeholders from "./__placeholders.md";
|
||||||
import EnvoyIstio from "./_envoy_istio.md";
|
import EnvoyIstio from "./_envoy_istio.md";
|
||||||
|
|
||||||
# Envoy
|
|
||||||
|
|
||||||
The configuration template shown below apply to both single-application and domain-level forward auth.
|
The configuration template shown below apply to both single-application and domain-level forward auth.
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Tabs from "@theme/Tabs";
|
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
|
||||||
# nginx
|
# nginx
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user