Compare commits
110 Commits
version-20
...
flow-no-we
Author | SHA1 | Date | |
---|---|---|---|
4aa497346d | |||
7f5cfdc3d3 | |||
5eb6d62c9c | |||
42501f6d1e | |||
2c802cad63 | |||
2759b1c089 | |||
ce6d76babe | |||
5cc2bd5b36 | |||
bad8a8ead5 | |||
1f7a2d5194 | |||
5e328403d6 | |||
f03e56af93 | |||
516aa9d9b1 | |||
c24fd618f5 | |||
c36434bfc8 | |||
1751d0ce17 | |||
7c386da474 | |||
b8112de172 | |||
a2644ca865 | |||
a036513669 | |||
44809b8d26 | |||
73b21a01d1 | |||
1e66a23172 | |||
44c50157b7 | |||
ab631e6d9b | |||
043e57ab2b | |||
989d39b154 | |||
1ed6999994 | |||
3bc8dd40d5 | |||
802d6a548c | |||
f82c6eda58 | |||
05cc64c434 | |||
a22b558143 | |||
bb2b6d163b | |||
199a2ff11a | |||
cc0659168d | |||
805332061b | |||
aa340fbfe0 | |||
91572b8621 | |||
080d31f189 | |||
15b59594e2 | |||
b4e295a14a | |||
b590b6be44 | |||
15ee3d3566 | |||
aea6c7adbe | |||
42a2337200 | |||
ffdd49e176 | |||
b41231141c | |||
88d3b7f5a4 | |||
2b39748c84 | |||
93b93517be | |||
6da55dc8aa | |||
b93dc48030 | |||
7aba4b0c01 | |||
d5572a2570 | |||
55b1ddff6e | |||
77c913bfd3 | |||
69b80e5bb5 | |||
ba63399a7b | |||
86893d83b8 | |||
85ab201803 | |||
2c96b24b62 | |||
1f2cbca833 | |||
c2db998041 | |||
18a70e93a1 | |||
3123b3ac5e | |||
f2e1b6d466 | |||
6bcacd744b | |||
e5af964d9d | |||
122b95197b | |||
8d4e7f5d55 | |||
9d32ba261a | |||
b5a9b645f4 | |||
46303cc59f | |||
4af415f3fd | |||
ef82143811 | |||
c7567e031a | |||
3b2cd9e8d6 | |||
261e18b3d6 | |||
51a0f7d314 | |||
041ffef812 | |||
68b4d58ebd | |||
881571bd14 | |||
64a0f66e62 | |||
7d5cda4c25 | |||
8ba2679036 | |||
d98523f243 | |||
6da0548fa2 | |||
8734710e61 | |||
64b996aa1f | |||
dbe91cbc55 | |||
a56e037eae | |||
b8f1e2fac0 | |||
e1b56aac05 | |||
794731eed7 | |||
19fbc2a022 | |||
38e467bf8e | |||
9e32cf361b | |||
42a5a43640 | |||
8d5b835c4f | |||
ca3b948895 | |||
a714c781a6 | |||
df2e3878d5 | |||
1370c32aea | |||
0ae373bc1e | |||
6facb5872e | |||
c67de17dd8 | |||
2128e7f45f | |||
0e7a4849f6 | |||
85343fa5d4 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2025.2.4
|
||||
current_version = 2025.2.1
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -28,7 +28,11 @@ Output of docker-compose logs or kubectl logs respectively
|
||||
|
||||
**Version and Deployment (please complete the following information):**
|
||||
|
||||
- authentik version: [e.g. 2021.8.5]
|
||||
<!--
|
||||
Notice: authentik supports installation via Docker, Kubernetes, and AWS CloudFormation only. Support is not available for other methods. For detailed installation and configuration instructions, please refer to the official documentation at https://docs.goauthentik.io/docs/install-config/.
|
||||
-->
|
||||
|
||||
- authentik version: [e.g. 2025.2.0]
|
||||
- Deployment: [e.g. docker-compose, helm]
|
||||
|
||||
**Additional context**
|
||||
|
7
.github/ISSUE_TEMPLATE/question.md
vendored
7
.github/ISSUE_TEMPLATE/question.md
vendored
@ -20,7 +20,12 @@ Output of docker-compose logs or kubectl logs respectively
|
||||
|
||||
**Version and Deployment (please complete the following information):**
|
||||
|
||||
- authentik version: [e.g. 2021.8.5]
|
||||
<!--
|
||||
Notice: authentik supports installation via Docker, Kubernetes, and AWS CloudFormation only. Support is not available for other methods. For detailed installation and configuration instructions, please refer to the official documentation at https://docs.goauthentik.io/docs/install-config/.
|
||||
-->
|
||||
|
||||
|
||||
- authentik version: [e.g. 2025.2.0]
|
||||
- Deployment: [e.g. docker-compose, helm]
|
||||
|
||||
**Additional context**
|
||||
|
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@ -35,7 +35,7 @@ runs:
|
||||
run: |
|
||||
export PSQL_TAG=${{ inputs.postgresql_version }}
|
||||
docker compose -f .github/actions/setup/docker-compose.yml up -d
|
||||
poetry install --sync
|
||||
poetry sync
|
||||
cd web && npm ci
|
||||
- name: Generate config
|
||||
shell: poetry run python {0}
|
||||
|
@ -40,7 +40,7 @@ jobs:
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-qemu-action@v3.4.0
|
||||
- uses: docker/setup-qemu-action@v3.5.0
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
|
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -82,7 +82,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.4.0
|
||||
uses: docker/setup-qemu-action@v3.5.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
|
2
.github/workflows/release-publish.yml
vendored
2
.github/workflows/release-publish.yml
vendored
@ -42,7 +42,7 @@ jobs:
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.4.0
|
||||
uses: docker/setup-qemu-action@v3.5.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
|
@ -1,9 +1,13 @@
|
||||
---
|
||||
name: authentik-backend-translate-extract-compile
|
||||
name: authentik-translate-extract-compile
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # every day at midnight
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- version-*
|
||||
|
||||
env:
|
||||
POSTGRES_DB: authentik
|
||||
@ -15,15 +19,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: generate_token
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Generate API
|
||||
run: make gen-client-ts
|
||||
- name: run extract
|
||||
run: |
|
||||
poetry run make i18n-extract
|
||||
@ -32,6 +42,7 @@ jobs:
|
||||
poetry run ak compilemessages
|
||||
make web-check-compile
|
||||
- name: Create Pull Request
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2025.2.4"
|
||||
__version__ = "2025.2.1"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ class SystemInfoSerializer(PassiveSerializer):
|
||||
if not isinstance(value, str):
|
||||
continue
|
||||
actual_value = value
|
||||
if raw_session is not None and raw_session in actual_value:
|
||||
if raw_session in actual_value:
|
||||
actual_value = actual_value.replace(
|
||||
raw_session, SafeExceptionReporterFilter.cleansed_substitute
|
||||
)
|
||||
|
@ -1,14 +1,13 @@
|
||||
"""User API Views"""
|
||||
|
||||
from datetime import timedelta
|
||||
from importlib import import_module
|
||||
from json import loads
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.sessions.backends.base import SessionBase
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.core.cache import cache
|
||||
from django.db.models.functions import ExtractHour
|
||||
from django.db.transaction import atomic
|
||||
from django.db.utils import IntegrityError
|
||||
@ -92,7 +91,6 @@ from authentik.stages.email.tasks import send_mails
|
||||
from authentik.stages.email.utils import TemplateEmailMessage
|
||||
|
||||
LOGGER = get_logger()
|
||||
SessionStore: SessionBase = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
class UserGroupSerializer(ModelSerializer):
|
||||
@ -375,7 +373,7 @@ class UsersFilter(FilterSet):
|
||||
method="filter_attributes",
|
||||
)
|
||||
|
||||
is_superuser = BooleanFilter(field_name="ak_groups", method="filter_is_superuser")
|
||||
is_superuser = BooleanFilter(field_name="ak_groups", lookup_expr="is_superuser")
|
||||
uuid = UUIDFilter(field_name="uuid")
|
||||
|
||||
path = CharFilter(field_name="path")
|
||||
@ -393,11 +391,6 @@ class UsersFilter(FilterSet):
|
||||
queryset=Group.objects.all().order_by("name"),
|
||||
)
|
||||
|
||||
def filter_is_superuser(self, queryset, name, value):
|
||||
if value:
|
||||
return queryset.filter(ak_groups__is_superuser=True).distinct()
|
||||
return queryset.exclude(ak_groups__is_superuser=True).distinct()
|
||||
|
||||
def filter_attributes(self, queryset, name, value):
|
||||
"""Filter attributes by query args"""
|
||||
try:
|
||||
@ -776,8 +769,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
if not instance.is_active:
|
||||
sessions = AuthenticatedSession.objects.filter(user=instance)
|
||||
session_ids = sessions.values_list("session_key", flat=True)
|
||||
for session in session_ids:
|
||||
SessionStore(session).delete()
|
||||
cache.delete_many(f"{KEY_PREFIX}{session}" for session in session_ids)
|
||||
sessions.delete()
|
||||
LOGGER.debug("Deleted user's sessions", user=instance.username)
|
||||
return response
|
||||
|
@ -1,10 +1,7 @@
|
||||
"""authentik core signals"""
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.contrib.sessions.backends.base import SessionBase
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.core.cache import cache
|
||||
from django.core.signals import Signal
|
||||
from django.db.models import Model
|
||||
@ -28,7 +25,6 @@ password_changed = Signal()
|
||||
login_failed = Signal()
|
||||
|
||||
LOGGER = get_logger()
|
||||
SessionStore: SessionBase = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
@receiver(post_save, sender=Application)
|
||||
@ -64,7 +60,8 @@ def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
def authenticated_session_delete(sender: type[Model], instance: "AuthenticatedSession", **_):
|
||||
"""Delete session when authenticated session is deleted"""
|
||||
SessionStore(instance.session_key).delete()
|
||||
cache_key = f"{KEY_PREFIX}{instance.session_key}"
|
||||
cache.delete(cache_key)
|
||||
|
||||
|
||||
@receiver(pre_save)
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Test Users API"""
|
||||
|
||||
from datetime import datetime
|
||||
from json import loads
|
||||
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.core.cache import cache
|
||||
@ -16,11 +15,7 @@ from authentik.core.models import (
|
||||
User,
|
||||
UserTypes,
|
||||
)
|
||||
from authentik.core.tests.utils import (
|
||||
create_test_admin_user,
|
||||
create_test_brand,
|
||||
create_test_flow,
|
||||
)
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_brand, create_test_flow
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.stages.email.models import EmailStage
|
||||
@ -46,32 +41,6 @@ class TestUsersAPI(APITestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_filter_is_superuser(self):
|
||||
"""Test API filtering by superuser status"""
|
||||
self.client.force_login(self.admin)
|
||||
# Test superuser
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:user-list"),
|
||||
data={
|
||||
"is_superuser": True,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content)
|
||||
self.assertEqual(len(body["results"]), 1)
|
||||
self.assertEqual(body["results"][0]["username"], self.admin.username)
|
||||
# Test non-superuser
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:user-list"),
|
||||
data={
|
||||
"is_superuser": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content)
|
||||
self.assertEqual(len(body["results"]), 1, body)
|
||||
self.assertEqual(body["results"][0]["username"], self.user.username)
|
||||
|
||||
def test_list_with_groups(self):
|
||||
"""Test listing with groups"""
|
||||
self.client.force_login(self.admin)
|
||||
|
@ -55,7 +55,7 @@ class RedirectToAppLaunch(View):
|
||||
)
|
||||
except FlowNonApplicableException:
|
||||
raise Http404 from None
|
||||
plan.append_stage(in_memory_stage(RedirectToAppStage))
|
||||
plan.insert_stage(in_memory_stage(RedirectToAppStage))
|
||||
return plan.to_redirect(request, flow)
|
||||
|
||||
|
||||
|
@ -89,9 +89,9 @@ class SourceStageFinal(StageView):
|
||||
This stage uses the override flow token to resume execution of the initial flow the
|
||||
source stage is bound to."""
|
||||
|
||||
def dispatch(self, *args, **kwargs):
|
||||
def dispatch(self):
|
||||
token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
|
||||
self.logger.info("Replacing source flow with overridden flow", flow=token.flow.slug)
|
||||
self._logger.info("Replacing source flow with overridden flow", flow=token.flow.slug)
|
||||
plan = token.plan
|
||||
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
|
||||
response = plan.to_redirect(self.request, token.flow)
|
||||
|
@ -4,8 +4,7 @@ from django.urls import reverse
|
||||
|
||||
from authentik.core.tests.utils import create_test_flow, create_test_user
|
||||
from authentik.enterprise.stages.source.models import SourceStage
|
||||
from authentik.enterprise.stages.source.stage import SourceStageFinal
|
||||
from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken, in_memory_stage
|
||||
from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken
|
||||
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, FlowPlan
|
||||
from authentik.flows.tests import FlowTestCase
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
@ -88,7 +87,6 @@ class TestSourceStage(FlowTestCase):
|
||||
self.assertIsNotNone(flow_token)
|
||||
session = self.client.session
|
||||
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
||||
plan.insert_stage(in_memory_stage(SourceStageFinal), index=0)
|
||||
plan.context[PLAN_CONTEXT_IS_RESTORED] = flow_token
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
@ -98,6 +96,4 @@ class TestSourceStage(FlowTestCase):
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageRedirects(
|
||||
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||
)
|
||||
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||
|
@ -8,7 +8,13 @@ from uuid import UUID
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db import models
|
||||
from django.http import JsonResponse
|
||||
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField
|
||||
from rest_framework.fields import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
ChoiceField,
|
||||
DictField,
|
||||
ListField,
|
||||
)
|
||||
from rest_framework.request import Request
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
@ -39,6 +45,12 @@ class ErrorDetailSerializer(PassiveSerializer):
|
||||
code = CharField()
|
||||
|
||||
|
||||
class MessageSerializer(PassiveSerializer):
|
||||
message = CharField()
|
||||
level = CharField()
|
||||
tags = ListField(child=CharField())
|
||||
|
||||
|
||||
class ContextualFlowInfo(PassiveSerializer):
|
||||
"""Contextual flow information for a challenge"""
|
||||
|
||||
@ -55,6 +67,7 @@ class Challenge(PassiveSerializer):
|
||||
flow_info = ContextualFlowInfo(required=False)
|
||||
component = CharField(default="")
|
||||
|
||||
messages = ListField(child=MessageSerializer(), allow_empty=True, required=False)
|
||||
response_errors = DictField(
|
||||
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False
|
||||
)
|
||||
@ -170,7 +183,6 @@ class FrameChallenge(Challenge):
|
||||
|
||||
|
||||
class FrameChallengeResponse(ChallengeResponse):
|
||||
|
||||
component = CharField(default="xak-flow-frame")
|
||||
|
||||
|
||||
|
@ -76,10 +76,10 @@ class FlowPlan:
|
||||
self.bindings.append(binding)
|
||||
self.markers.append(marker or StageMarker())
|
||||
|
||||
def insert_stage(self, stage: Stage, marker: StageMarker | None = None, index=1):
|
||||
def insert_stage(self, stage: Stage, marker: StageMarker | None = None):
|
||||
"""Insert stage into plan, as immediate next stage"""
|
||||
self.bindings.insert(index, FlowStageBinding(stage=stage, order=0))
|
||||
self.markers.insert(index, marker or StageMarker())
|
||||
self.bindings.insert(1, FlowStageBinding(stage=stage, order=0))
|
||||
self.markers.insert(1, marker or StageMarker())
|
||||
|
||||
def redirect(self, destination: str):
|
||||
"""Insert a redirect stage as next stage"""
|
||||
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.messages import get_messages
|
||||
from django.http import HttpRequest
|
||||
from django.http.request import QueryDict
|
||||
from django.http.response import HttpResponse
|
||||
@ -21,6 +22,7 @@ from authentik.flows.challenge import (
|
||||
ChallengeResponse,
|
||||
ContextualFlowInfo,
|
||||
HttpChallengeResponse,
|
||||
MessageSerializer,
|
||||
RedirectChallenge,
|
||||
SessionEndChallenge,
|
||||
WithUserInfoChallenge,
|
||||
@ -191,6 +193,22 @@ class ChallengeStageView(StageView):
|
||||
)
|
||||
flow_info.is_valid()
|
||||
challenge.initial_data["flow_info"] = flow_info.data
|
||||
if "messages" not in challenge.initial_data and not isinstance(
|
||||
challenge, RedirectStage
|
||||
):
|
||||
messages = MessageSerializer(
|
||||
data=[
|
||||
{
|
||||
"message": message.message,
|
||||
"level": message.level_tag,
|
||||
"tags": message.tags,
|
||||
}
|
||||
for message in get_messages(self.request)
|
||||
],
|
||||
many=True,
|
||||
)
|
||||
messages.is_valid()
|
||||
challenge.initial_data["messages"] = messages.data
|
||||
if isinstance(challenge, WithUserInfoChallenge):
|
||||
# If there's a pending user, update the `username` field
|
||||
# this field is only used by password managers.
|
||||
|
@ -55,6 +55,7 @@ class TestFlowInspector(APITestCase):
|
||||
"layout": "stacked",
|
||||
},
|
||||
"flow_designation": "authentication",
|
||||
"messages": [],
|
||||
"password_fields": False,
|
||||
"primary_action": "Log in",
|
||||
"sources": [],
|
||||
|
@ -282,14 +282,16 @@ class ConfigLoader:
|
||||
|
||||
def get_optional_int(self, path: str, default=None) -> int | None:
|
||||
"""Wrapper for get that converts value into int or None if set"""
|
||||
value = self.get(path, UNSET)
|
||||
value = self.get(path, default)
|
||||
if value is UNSET:
|
||||
return default
|
||||
try:
|
||||
return int(value)
|
||||
except (ValueError, TypeError) as exc:
|
||||
if value is None or (isinstance(value, str) and value.lower() == "null"):
|
||||
return None
|
||||
return default
|
||||
if value is UNSET:
|
||||
return default
|
||||
self.log("warning", "Failed to parse config as int", path=path, exc=str(exc))
|
||||
return default
|
||||
|
||||
@ -370,9 +372,9 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
|
||||
"sslcert": config.get("postgresql.sslcert"),
|
||||
"sslkey": config.get("postgresql.sslkey"),
|
||||
},
|
||||
"CONN_MAX_AGE": config.get_optional_int("postgresql.conn_max_age", 0),
|
||||
"CONN_HEALTH_CHECKS": config.get_bool("postgresql.conn_health_checks", False),
|
||||
"DISABLE_SERVER_SIDE_CURSORS": config.get_bool(
|
||||
"CONN_MAX_AGE": CONFIG.get_optional_int("postgresql.conn_max_age", 0),
|
||||
"CONN_HEALTH_CHECKS": CONFIG.get_bool("postgresql.conn_health_checks", False),
|
||||
"DISABLE_SERVER_SIDE_CURSORS": CONFIG.get_bool(
|
||||
"postgresql.disable_server_side_cursors", False
|
||||
),
|
||||
"TEST": {
|
||||
@ -381,8 +383,8 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
|
||||
}
|
||||
}
|
||||
|
||||
conn_max_age = config.get_optional_int("postgresql.conn_max_age", UNSET)
|
||||
disable_server_side_cursors = config.get_bool("postgresql.disable_server_side_cursors", UNSET)
|
||||
conn_max_age = CONFIG.get_optional_int("postgresql.conn_max_age", UNSET)
|
||||
disable_server_side_cursors = CONFIG.get_bool("postgresql.disable_server_side_cursors", UNSET)
|
||||
if config.get_bool("postgresql.use_pgpool", False):
|
||||
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
||||
if disable_server_side_cursors is not UNSET:
|
||||
|
@ -64,6 +64,8 @@ debugger: false
|
||||
log_level: info
|
||||
|
||||
session_storage: cache
|
||||
sessions:
|
||||
unauthenticated_age: days=1
|
||||
|
||||
error_reporting:
|
||||
enabled: false
|
||||
|
@ -158,18 +158,6 @@ class TestConfig(TestCase):
|
||||
test_obj = Test()
|
||||
dumps(test_obj, indent=4, cls=AttrEncoder)
|
||||
|
||||
def test_get_optional_int(self):
|
||||
config = ConfigLoader()
|
||||
self.assertEqual(config.get_optional_int("foo", 21), 21)
|
||||
self.assertEqual(config.get_optional_int("foo"), None)
|
||||
config.set("foo", "21")
|
||||
self.assertEqual(config.get_optional_int("foo"), 21)
|
||||
self.assertEqual(config.get_optional_int("foo", 0), 21)
|
||||
self.assertEqual(config.get_optional_int("foo", "null"), 21)
|
||||
config.set("foo", "null")
|
||||
self.assertEqual(config.get_optional_int("foo"), None)
|
||||
self.assertEqual(config.get_optional_int("foo", 21), None)
|
||||
|
||||
@mock.patch.dict(environ, check_deprecations_env_vars)
|
||||
def test_check_deprecations(self):
|
||||
"""Test config key re-write for deprecated env vars"""
|
||||
@ -233,16 +221,6 @@ class TestConfig(TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_db_conn_max_age(self):
|
||||
"""Test DB conn_max_age Config"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.conn_max_age", "null")
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf["default"]["CONN_MAX_AGE"],
|
||||
None,
|
||||
)
|
||||
|
||||
def test_db_read_replicas(self):
|
||||
"""Test read replicas"""
|
||||
config = ConfigLoader()
|
||||
|
@ -88,6 +88,7 @@ class TesOAuth2DeviceInit(OAuthTestCase):
|
||||
"layout": "stacked",
|
||||
"title": self.device_flow.title,
|
||||
},
|
||||
"messages": [],
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -71,7 +71,7 @@ class CodeValidatorView(PolicyAccessView):
|
||||
except FlowNonApplicableException:
|
||||
LOGGER.warning("Flow not applicable to user")
|
||||
return None
|
||||
plan.append_stage(in_memory_stage(OAuthDeviceCodeFinishStage))
|
||||
plan.insert_stage(in_memory_stage(OAuthDeviceCodeFinishStage))
|
||||
return plan.to_redirect(self.request, self.token.provider.authorization_flow)
|
||||
|
||||
|
||||
|
@ -34,5 +34,5 @@ class EndSessionView(PolicyAccessView):
|
||||
PLAN_CONTEXT_APPLICATION: self.application,
|
||||
},
|
||||
)
|
||||
plan.append_stage(in_memory_stage(SessionEndStage))
|
||||
plan.insert_stage(in_memory_stage(SessionEndStage))
|
||||
return plan.to_redirect(self.request, self.flow)
|
||||
|
@ -36,17 +36,17 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
||||
def reconciler_name() -> str:
|
||||
return "ingress"
|
||||
|
||||
def _check_annotations(self, current: V1Ingress, reference: V1Ingress):
|
||||
def _check_annotations(self, reference: V1Ingress):
|
||||
"""Check that all annotations *we* set are correct"""
|
||||
for key, value in reference.metadata.annotations.items():
|
||||
if key not in current.metadata.annotations:
|
||||
for key, value in self.get_ingress_annotations().items():
|
||||
if key not in reference.metadata.annotations:
|
||||
raise NeedsUpdate()
|
||||
if current.metadata.annotations[key] != value:
|
||||
if reference.metadata.annotations[key] != value:
|
||||
raise NeedsUpdate()
|
||||
|
||||
def reconcile(self, current: V1Ingress, reference: V1Ingress):
|
||||
super().reconcile(current, reference)
|
||||
self._check_annotations(current, reference)
|
||||
self._check_annotations(reference)
|
||||
# Create a list of all expected host and tls hosts
|
||||
expected_hosts = []
|
||||
expected_hosts_tls = []
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""RAC app config"""
|
||||
|
||||
from authentik.blueprints.apps import ManagedAppConfig
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuthentikProviderRAC(ManagedAppConfig):
|
||||
class AuthentikProviderRAC(AppConfig):
|
||||
"""authentik rac app config"""
|
||||
|
||||
name = "authentik.providers.rac"
|
||||
|
@ -4,7 +4,8 @@ from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django.contrib.auth.signals import user_logged_out
|
||||
from django.core.cache import cache
|
||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.http import HttpRequest
|
||||
|
||||
@ -45,8 +46,12 @@ def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, **
|
||||
)
|
||||
|
||||
|
||||
@receiver([post_save, post_delete], sender=Endpoint)
|
||||
def post_save_post_delete_endpoint(**_):
|
||||
"""Clear user's endpoint cache upon endpoint creation or deletion"""
|
||||
@receiver(post_save, sender=Endpoint)
|
||||
def post_save_endpoint(sender: type[Model], instance, created: bool, **_):
|
||||
"""Clear user's endpoint cache upon endpoint creation"""
|
||||
if not created: # pragma: no cover
|
||||
return
|
||||
|
||||
# Delete user endpoint cache
|
||||
keys = cache.keys(user_endpoint_cache_key("*"))
|
||||
cache.delete_many(keys)
|
||||
|
@ -46,7 +46,7 @@ class RACStartView(PolicyAccessView):
|
||||
)
|
||||
except FlowNonApplicableException:
|
||||
raise Http404 from None
|
||||
plan.append_stage(
|
||||
plan.insert_stage(
|
||||
in_memory_stage(
|
||||
RACFinalStage,
|
||||
application=self.application,
|
||||
|
@ -61,7 +61,7 @@ class SAMLSLOView(PolicyAccessView):
|
||||
PLAN_CONTEXT_APPLICATION: self.application,
|
||||
},
|
||||
)
|
||||
plan.append_stage(in_memory_stage(SessionEndStage))
|
||||
plan.insert_stage(in_memory_stage(SessionEndStage))
|
||||
return plan.to_redirect(self.request, self.flow)
|
||||
|
||||
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
|
||||
|
@ -243,10 +243,9 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
if user.value not in users_should:
|
||||
users_to_remove.append(user.value)
|
||||
# Check users that should be in the group and add them
|
||||
if current_group.members is not None:
|
||||
for user in users_should:
|
||||
if len([x for x in current_group.members if x.value == user]) < 1:
|
||||
users_to_add.append(user)
|
||||
for user in users_should:
|
||||
if len([x for x in current_group.members if x.value == user]) < 1:
|
||||
users_to_add.append(user)
|
||||
# Only send request if we need to make changes
|
||||
if len(users_to_add) < 1 and len(users_to_remove) < 1:
|
||||
return
|
||||
|
@ -1,12 +1,10 @@
|
||||
"""User client"""
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils.http import urlencode
|
||||
from pydantic import ValidationError
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.lib.sync.mapper import PropertyMappingManager
|
||||
from authentik.lib.sync.outgoing.exceptions import ObjectExistsSyncException, StopSync
|
||||
from authentik.lib.sync.outgoing.exceptions import StopSync
|
||||
from authentik.policies.utils import delete_none_values
|
||||
from authentik.providers.scim.clients.base import SCIMClient
|
||||
from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
|
||||
@ -57,35 +55,18 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
|
||||
def create(self, user: User):
|
||||
"""Create user from scratch and create a connection object"""
|
||||
scim_user = self.to_schema(user, None)
|
||||
with transaction.atomic():
|
||||
try:
|
||||
response = self._request(
|
||||
"POST",
|
||||
"/Users",
|
||||
json=scim_user.model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
except ObjectExistsSyncException as exc:
|
||||
if not self._config.filter.supported:
|
||||
raise exc
|
||||
users = self._request(
|
||||
"GET", f"/Users?{urlencode({'filter': f'userName eq {scim_user.userName}'})}"
|
||||
)
|
||||
users_res = users.get("Resources", [])
|
||||
if len(users_res) < 1:
|
||||
raise exc
|
||||
return SCIMProviderUser.objects.create(
|
||||
provider=self.provider, user=user, scim_id=users_res[0]["id"]
|
||||
)
|
||||
else:
|
||||
scim_id = response.get("id")
|
||||
if not scim_id or scim_id == "":
|
||||
raise StopSync("SCIM Response with missing or invalid `id`")
|
||||
return SCIMProviderUser.objects.create(
|
||||
provider=self.provider, user=user, scim_id=scim_id
|
||||
)
|
||||
response = self._request(
|
||||
"POST",
|
||||
"/Users",
|
||||
json=scim_user.model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
scim_id = response.get("id")
|
||||
if not scim_id or scim_id == "":
|
||||
raise StopSync("SCIM Response with missing or invalid `id`")
|
||||
return SCIMProviderUser.objects.create(provider=self.provider, user=user, scim_id=scim_id)
|
||||
|
||||
def update(self, user: User, connection: SCIMProviderUser):
|
||||
"""Update existing user"""
|
||||
|
@ -7,7 +7,6 @@ from django.contrib.messages.storage.session import SessionStorage
|
||||
from django.core.cache import cache
|
||||
from django.http.request import HttpRequest
|
||||
|
||||
SESSION_KEY = "_messages"
|
||||
CACHE_PREFIX = "goauthentik.io/root/messages_"
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ from authentik.lib.config import CONFIG, django_db_config, redis_url
|
||||
from authentik.lib.logging import get_logger_config, structlog_configure
|
||||
from authentik.lib.sentry import sentry_init
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
|
||||
|
||||
BASE_DIR = Path(__file__).absolute().parent.parent.parent
|
||||
@ -242,6 +243,9 @@ SESSION_CACHE_ALIAS = "default"
|
||||
# Configured via custom SessionMiddleware
|
||||
# SESSION_COOKIE_SAMESITE = "None"
|
||||
# SESSION_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_AGE = timedelta_from_string(
|
||||
CONFIG.get("sessions.unauthenticated_age", "days=1")
|
||||
).total_seconds()
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||
|
||||
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
|
||||
|
@ -68,6 +68,8 @@ class OAuth2Client(BaseOAuthClient):
|
||||
error_desc = self.get_request_arg("error_description", None)
|
||||
return {"error": error_desc or error or _("No token received.")}
|
||||
args = {
|
||||
"client_id": self.get_client_id(),
|
||||
"client_secret": self.get_client_secret(),
|
||||
"redirect_uri": callback,
|
||||
"code": code,
|
||||
"grant_type": "authorization_code",
|
||||
|
@ -28,7 +28,7 @@ def update_well_known_jwks(self: SystemTask):
|
||||
LOGGER.warning("Failed to update well_known", source=source, exc=exc, text=text)
|
||||
messages.append(f"Failed to update OIDC configuration for {source.slug}")
|
||||
continue
|
||||
config: dict = well_known_config.json()
|
||||
config = well_known_config.json()
|
||||
try:
|
||||
dirty = False
|
||||
source_attr_key = (
|
||||
@ -40,9 +40,7 @@ def update_well_known_jwks(self: SystemTask):
|
||||
for source_attr, config_key in source_attr_key:
|
||||
# Check if we're actually changing anything to only
|
||||
# save when something has changed
|
||||
if config_key not in config:
|
||||
continue
|
||||
if getattr(source, source_attr, "") != config.get(config_key, ""):
|
||||
if getattr(source, source_attr, "") != config[config_key]:
|
||||
dirty = True
|
||||
setattr(source, source_attr, config[config_key])
|
||||
except (IndexError, KeyError) as exc:
|
||||
|
@ -7,7 +7,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from rest_framework.serializers import BaseSerializer
|
||||
|
||||
from authentik.core.types import UserSettingSerializer
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.exceptions import StageInvalidException
|
||||
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
|
||||
@ -72,14 +71,6 @@ class AuthenticatorEmailStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
||||
def component(self) -> str:
|
||||
return "ak-stage-authenticator-email-form"
|
||||
|
||||
def ui_user_settings(self) -> UserSettingSerializer | None:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
"title": self.friendly_name or str(self._meta.verbose_name),
|
||||
"component": "ak-user-settings-authenticator-email",
|
||||
}
|
||||
)
|
||||
|
||||
@property
|
||||
def backend_class(self) -> type[BaseEmailBackend]:
|
||||
"""Get the email backend class to use"""
|
||||
|
@ -145,9 +145,8 @@ class EmailStageView(ChallengeStageView):
|
||||
user.save()
|
||||
return self.executor.stage_ok()
|
||||
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
||||
self.logger.debug("No pending user")
|
||||
messages.error(self.request, _("No pending user."))
|
||||
return self.executor.stage_invalid()
|
||||
self.logger.warning("No pending user")
|
||||
return self.executor.stage_invalid(_("No pending user"))
|
||||
# Check if we've already sent the initial e-mail
|
||||
if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context:
|
||||
try:
|
||||
|
@ -104,13 +104,6 @@ def send_mail(
|
||||
# can't be converted to json)
|
||||
message_object.attach(logo_data())
|
||||
|
||||
if (
|
||||
message_object.to
|
||||
and isinstance(message_object.to[0], str)
|
||||
and "=?utf-8?" in message_object.to[0]
|
||||
):
|
||||
message_object.to = [message_object.to[0].split("<")[-1].replace(">", "")]
|
||||
|
||||
LOGGER.debug("Sending mail", to=message_object.to)
|
||||
backend.send_messages([message_object])
|
||||
Event.new(
|
||||
|
@ -8,7 +8,7 @@ from django.core.mail.backends.locmem import EmailBackend
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_user
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.markers import StageMarker
|
||||
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
||||
@ -67,67 +67,6 @@ class TestEmailStageSending(FlowTestCase):
|
||||
self.assertEqual(event.context["to_email"], [f"{self.user.name} <{self.user.email}>"])
|
||||
self.assertEqual(event.context["from_email"], "system@authentik.local")
|
||||
|
||||
def test_newlines_long_name(self):
|
||||
"""Test with pending user"""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
long_user = create_test_user()
|
||||
long_user.name = "Test User\r\n Many Words\r\n"
|
||||
long_user.save()
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = long_user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
Event.objects.filter(action=EventAction.EMAIL_SENT).delete()
|
||||
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
with patch(
|
||||
"authentik.stages.email.models.EmailStage.backend_class",
|
||||
PropertyMock(return_value=EmailBackend),
|
||||
):
|
||||
response = self.client.post(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
self.flow,
|
||||
response_errors={
|
||||
"non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
|
||||
},
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||
self.assertEqual(mail.outbox[0].to, [f"Test User Many Words <{long_user.email}>"])
|
||||
|
||||
def test_utf8_name(self):
|
||||
"""Test with pending user"""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
utf8_user = create_test_user()
|
||||
utf8_user.name = "Cirilo ЉМНЊ el cirilico И̂ӢЙӤ "
|
||||
utf8_user.email = "cyrillic@authentik.local"
|
||||
utf8_user.save()
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = utf8_user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
Event.objects.filter(action=EventAction.EMAIL_SENT).delete()
|
||||
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
with patch(
|
||||
"authentik.stages.email.models.EmailStage.backend_class",
|
||||
PropertyMock(return_value=EmailBackend),
|
||||
):
|
||||
response = self.client.post(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
self.flow,
|
||||
response_errors={
|
||||
"non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
|
||||
},
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||
self.assertEqual(mail.outbox[0].to, [f"{utf8_user.email}"])
|
||||
|
||||
def test_pending_fake_user(self):
|
||||
"""Test with pending (fake) user"""
|
||||
self.flow.designation = FlowDesignation.RECOVERY
|
||||
|
@ -32,14 +32,7 @@ class TemplateEmailMessage(EmailMultiAlternatives):
|
||||
sanitized_to = []
|
||||
# Ensure that all recipients are valid
|
||||
for recipient_name, recipient_email in to:
|
||||
# Remove any newline characters from name and email before sanitizing
|
||||
clean_name = (
|
||||
recipient_name.replace("\n", " ").replace("\r", " ") if recipient_name else ""
|
||||
)
|
||||
clean_email = (
|
||||
recipient_email.replace("\n", "").replace("\r", "") if recipient_email else ""
|
||||
)
|
||||
sanitized_to.append(sanitize_address((clean_name, clean_email), "utf-8"))
|
||||
sanitized_to.append(sanitize_address((recipient_name, recipient_email), "utf-8"))
|
||||
super().__init__(to=sanitized_to, **kwargs)
|
||||
if not template_name:
|
||||
return
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Delete stage logic"""
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.translation import gettext as _
|
||||
@ -16,10 +15,8 @@ class UserDeleteStageView(StageView):
|
||||
"""Delete currently pending user"""
|
||||
user = self.get_pending_user()
|
||||
if not user.is_authenticated:
|
||||
message = _("No Pending User.")
|
||||
messages.error(request, message)
|
||||
self.logger.debug(message)
|
||||
return self.executor.stage_invalid()
|
||||
self.logger.warning("No authenticated user")
|
||||
return self.executor.stage_invalid(_("No authenticated User."))
|
||||
logout(self.request)
|
||||
user.delete()
|
||||
self.logger.debug("Deleted user", user=user)
|
||||
|
@ -80,10 +80,8 @@ class UserLoginStageView(ChallengeStageView):
|
||||
def do_login(self, request: HttpRequest, remember: bool = False) -> HttpResponse:
|
||||
"""Attach the currently pending user to the current session"""
|
||||
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
||||
message = _("No Pending user to login.")
|
||||
messages.error(request, message)
|
||||
self.logger.debug(message)
|
||||
return self.executor.stage_invalid()
|
||||
self.logger.warning("No pending user to login")
|
||||
return self.executor.stage_invalid(_("No Pending user to login."))
|
||||
backend = self.executor.plan.context.get(
|
||||
PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT
|
||||
)
|
||||
|
@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2025.2.4 Blueprint schema",
|
||||
"title": "authentik 2025.2.1 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
|
@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.4}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -54,7 +54,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.4}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
24
go.mod
24
go.mod
@ -1,8 +1,8 @@
|
||||
module goauthentik.io
|
||||
|
||||
go 1.23
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.0
|
||||
toolchain go1.24.0
|
||||
|
||||
require (
|
||||
beryju.io/ldap v0.1.0
|
||||
@ -22,16 +22,16 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.8.0
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/prometheus/client_golang v1.21.0
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/sethvargo/go-envconfig v1.1.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2024123.6
|
||||
goauthentik.io/api/v3 v3.2025020.1
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.26.0
|
||||
golang.org/x/oauth2 v0.27.0
|
||||
golang.org/x/sync v0.11.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||
@ -48,7 +48,7 @@ require (
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
@ -62,23 +62,23 @@ require (
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
38
go.sum
38
go.sum
@ -84,8 +84,8 @@ github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2D
|
||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@ -207,8 +207,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@ -239,17 +239,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
|
||||
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
|
||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2024123.6 h1:AGOCa7Fc/9eONCPEW4sEhTiyEBvxN57Lfqz1zm6Gy98=
|
||||
goauthentik.io/api/v3 v3.2024123.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2025020.1 h1:7922W4XiGif7lUCl2qlaeQJ3wSx1wDDDpXx8ryx0Hv0=
|
||||
goauthentik.io/api/v3 v3.2025020.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -312,8 +312,9 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -393,8 +394,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
|
||||
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -447,8 +448,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@ -595,8 +597,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2025.2.4"
|
||||
const VERSION = "2025.2.1"
|
||||
|
@ -35,19 +35,13 @@ func Paginator[Tobj any, Treq any, Tres PaginatorResponse[Tobj]](
|
||||
req PaginatorRequest[Treq, Tres],
|
||||
opts PaginatorOptions,
|
||||
) ([]Tobj, error) {
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = log.NewEntry(log.StandardLogger())
|
||||
}
|
||||
var bfreq, cfreq interface{}
|
||||
fetchOffset := func(page int32) (Tres, error) {
|
||||
bfreq = req.Page(page)
|
||||
cfreq = bfreq.(PaginatorRequest[Treq, Tres]).PageSize(int32(opts.PageSize))
|
||||
res, hres, err := cfreq.(PaginatorRequest[Treq, Tres]).Execute()
|
||||
res, _, err := cfreq.(PaginatorRequest[Treq, Tres]).Execute()
|
||||
if err != nil {
|
||||
opts.Logger.WithError(err).WithField("page", page).Warning("failed to fetch page")
|
||||
if hres != nil && hres.StatusCode >= 400 && hres.StatusCode < 500 {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
@ -57,9 +51,6 @@ func Paginator[Tobj any, Treq any, Tres PaginatorResponse[Tobj]](
|
||||
for {
|
||||
apiObjects, err := fetchOffset(page)
|
||||
if err != nil {
|
||||
if page == 1 {
|
||||
return objects, err
|
||||
}
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
@ -1,64 +1,5 @@
|
||||
package ak
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goauthentik.io/api/v3"
|
||||
)
|
||||
|
||||
type fakeAPIType struct{}
|
||||
|
||||
type fakeAPIResponse struct {
|
||||
results []fakeAPIType
|
||||
pagination api.Pagination
|
||||
}
|
||||
|
||||
func (fapi *fakeAPIResponse) GetResults() []fakeAPIType { return fapi.results }
|
||||
func (fapi *fakeAPIResponse) GetPagination() api.Pagination { return fapi.pagination }
|
||||
|
||||
type fakeAPIRequest struct {
|
||||
res *fakeAPIResponse
|
||||
http *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
func (fapi *fakeAPIRequest) Page(page int32) *fakeAPIRequest { return fapi }
|
||||
func (fapi *fakeAPIRequest) PageSize(size int32) *fakeAPIRequest { return fapi }
|
||||
func (fapi *fakeAPIRequest) Execute() (*fakeAPIResponse, *http.Response, error) {
|
||||
return fapi.res, fapi.http, fapi.err
|
||||
}
|
||||
|
||||
func Test_Simple(t *testing.T) {
|
||||
req := &fakeAPIRequest{
|
||||
res: &fakeAPIResponse{
|
||||
results: []fakeAPIType{
|
||||
{},
|
||||
},
|
||||
pagination: api.Pagination{
|
||||
TotalPages: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := Paginator(req, PaginatorOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, res, 1)
|
||||
}
|
||||
|
||||
func Test_BadRequest(t *testing.T) {
|
||||
req := &fakeAPIRequest{
|
||||
http: &http.Response{
|
||||
StatusCode: 400,
|
||||
},
|
||||
err: errors.New("foo"),
|
||||
}
|
||||
res, err := Paginator(req, PaginatorOptions{})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, []fakeAPIType{}, res)
|
||||
}
|
||||
|
||||
// func Test_PaginatorCompile(t *testing.T) {
|
||||
// req := api.ApiCoreUsersListRequest{}
|
||||
// Paginator(req, PaginatorOptions{
|
||||
|
@ -82,8 +82,7 @@ if [[ "$1" == "server" ]]; then
|
||||
run_authentik
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
set_mode "worker"
|
||||
shift
|
||||
check_if_root "python -m manage worker $@"
|
||||
check_if_root "python -m manage worker"
|
||||
elif [[ "$1" == "worker-status" ]]; then
|
||||
wait_for_db
|
||||
celery -A authentik.root.celery flower \
|
||||
|
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.179.0",
|
||||
"aws-cdk": "^2.1001.0",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.179.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.179.0.tgz",
|
||||
"integrity": "sha512-aA2+8S2g4UBQHkUEt0mYd16VLt/ucR+QfyUJi34LDKRAhOCNDjPCZ4z9z/JEDyuni0BdzsYA55pnpDN9tMULpA==",
|
||||
"version": "2.1001.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1001.0.tgz",
|
||||
"integrity": "sha512-Wp6fKNXcxBm+f8U1GkLV4gEgqq1pu5uwyDCMBg7ZB/6CtP+PsD/mPhuKyMULNWucDvYN8oy70XLOkMnxa3NWFw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.179.0",
|
||||
"aws-cdk": "^2.1001.0",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.2.4
|
||||
Default: 2025.2.1
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-14 14:49+0000\n"
|
||||
"POT-Creation-Date: 2025-02-25 00:11+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -109,6 +109,10 @@ msgstr ""
|
||||
msgid "Extra description not available"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
"When not set all providers are returned. When set to true, only backchannel "
|
||||
@ -152,6 +156,14 @@ msgstr ""
|
||||
msgid "Remove user from group"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Enable superuser status"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Disable superuser status"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "User's display name."
|
||||
msgstr ""
|
||||
@ -500,57 +512,6 @@ msgstr ""
|
||||
msgid "Microsoft Entra Provider Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
#: authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Signing Key"
|
||||
@ -651,7 +612,7 @@ msgstr ""
|
||||
msgid "Slack Webhook (Slack/Discord)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py
|
||||
#: authentik/events/models.py authentik/stages/authenticator_validate/models.py
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
@ -1105,6 +1066,14 @@ msgstr ""
|
||||
msgid "Client IP is not in an allowed country."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance from previous authentication is larger than threshold."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance is further than possible."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "GeoIP Policy"
|
||||
msgstr ""
|
||||
@ -1643,6 +1612,56 @@ msgstr ""
|
||||
msgid "Proxy Providers"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/radius/models.py
|
||||
msgid "Shared secret between clients and server to hash packets."
|
||||
msgstr ""
|
||||
@ -2486,6 +2505,98 @@ msgstr ""
|
||||
msgid "Duo Devices"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email OTP"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stage"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stages"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Device"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Devices"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
msgid "Invalid email"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
msgid ""
|
||||
"\n"
|
||||
" Email MFA code.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" If you did not request this code, please ignore this email. The code "
|
||||
"above is valid for %(expires)s.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
"Email MFA code\n"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
"If you did not request this code, please ignore this email. The code above "
|
||||
"is valid for %(expires)s.\n"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_sms/models.py
|
||||
msgid ""
|
||||
"When enabled, the Phone number is only used during enrollment to verify the "
|
||||
@ -2518,11 +2629,6 @@ msgstr ""
|
||||
msgid "SMS Devices"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
msgid "Invalid phone number"
|
||||
msgstr ""
|
||||
@ -2745,12 +2851,6 @@ msgstr ""
|
||||
msgid "Account Confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Activate users upon completion of stage."
|
||||
msgstr ""
|
||||
@ -2767,10 +2867,6 @@ msgstr ""
|
||||
msgid "Email Stages"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Successfully verified Email."
|
||||
msgstr ""
|
||||
@ -2845,14 +2941,6 @@ msgid ""
|
||||
"This email was sent from the notification transport %(name)s.\n"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
msgid ""
|
||||
"\n"
|
||||
@ -2870,11 +2958,6 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -19,7 +19,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-14 14:49+0000\n"
|
||||
"POT-Creation-Date: 2025-02-25 00:11+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Marc Schmitt, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||
@ -129,6 +129,10 @@ msgstr "L'utilisateur n'a pas accès à l'application."
|
||||
msgid "Extra description not available"
|
||||
msgstr "Description supplémentaire indisponible"
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr "Impossible de définir le groupe en tant que parent de lui-même."
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
"When not set all providers are returned. When set to true, only backchannel "
|
||||
@ -177,6 +181,14 @@ msgstr "Ajouter un utilisateur au groupe"
|
||||
msgid "Remove user from group"
|
||||
msgstr "Retirer l'utilisateur du groupe"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Enable superuser status"
|
||||
msgstr "Activer le statut super-utilisateur"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Disable superuser status"
|
||||
msgstr "Désactiver le statut super-utilisateur"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "User's display name."
|
||||
msgstr "Nom d'affichage de l'utilisateur"
|
||||
@ -553,61 +565,6 @@ msgstr "Mappage de propriété Microsoft Entra"
|
||||
msgid "Microsoft Entra Provider Mappings"
|
||||
msgstr "Mappages de propriété Microsoft Entra"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
#: authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
"Détermine la durée de la session. La valeur par défaut de 0 signifie que la "
|
||||
"session dure jusqu'à la fermeture du navigateur. (Format : "
|
||||
"hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr ""
|
||||
"Si activé, les jetons de connexion seront supprimés lors de la déconnexion."
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "Fournisseur RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "Fournisseurs RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "Point de terminaison RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "Points de terminaison RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "Mappage de propriété fournisseur RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "Mappages de propriété fournisseur RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "Jeton de connexion RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "Jeton de connexions RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "Limite maximum de connection atteinte."
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(Vous êtes déjà connecté dans un autre onglet/une autre fenêtre)"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Signing Key"
|
||||
@ -715,6 +672,7 @@ msgid "Slack Webhook (Slack/Discord)"
|
||||
msgstr "Webhook Slack (ou Discord)"
|
||||
|
||||
#: authentik/events/models.py
|
||||
#: authentik/stages/authenticator_validate/models.py
|
||||
msgid "Email"
|
||||
msgstr "Courriel"
|
||||
|
||||
@ -1219,6 +1177,16 @@ msgstr ""
|
||||
msgid "Client IP is not in an allowed country."
|
||||
msgstr "L'IP du client ne fait pas partie d'un pays autorisé."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance from previous authentication is larger than threshold."
|
||||
msgstr ""
|
||||
"La distance par rapport à l'authentification précédente est supérieure au "
|
||||
"seuil."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance is further than possible."
|
||||
msgstr "La distance est plus grande que possible."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "GeoIP Policy"
|
||||
msgstr "Politique GeoIP"
|
||||
@ -1825,6 +1793,60 @@ msgstr "Fournisseur Proxy"
|
||||
msgid "Proxy Providers"
|
||||
msgstr "Fournisseur de Proxy"
|
||||
|
||||
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
"Détermine la durée de la session. La valeur par défaut de 0 signifie que la "
|
||||
"session dure jusqu'à la fermeture du navigateur. (Format : "
|
||||
"hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr ""
|
||||
"Si activé, les jetons de connexion seront supprimés lors de la déconnexion."
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "Fournisseur RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "Fournisseurs RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "Point de terminaison RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "Points de terminaison RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "Mappage de propriété fournisseur RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "Mappages de propriété fournisseur RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "Jeton de connexion RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "Jeton de connexions RAC"
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "Limite maximum de connection atteinte."
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(Vous êtes déjà connecté dans un autre onglet/une autre fenêtre)"
|
||||
|
||||
#: authentik/providers/radius/models.py
|
||||
msgid "Shared secret between clients and server to hash packets."
|
||||
msgstr ""
|
||||
@ -2741,6 +2763,112 @@ msgstr "Appareil Duo"
|
||||
msgid "Duo Devices"
|
||||
msgstr "Appareils Duo"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email OTP"
|
||||
msgstr "OTP Courriel"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr ""
|
||||
"Si activé, les paramètres globaux de connexion courriel seront utilisés et "
|
||||
"les paramètres de connexion ci-dessous seront ignorés."
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
|
||||
msgstr ""
|
||||
"Durée de validité du jeton envoyé (Format : hours=3,minutes=17,seconds=300)."
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stage"
|
||||
msgstr "Étape de configuration de l'authentificateur courriel"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stages"
|
||||
msgstr "Étapes de configuration de l'authentificateur courriel"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr "Une erreur s'est produite lors de la modélisation du couriel"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Device"
|
||||
msgstr "Équipement courriel"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Devices"
|
||||
msgstr "Équipements courriel"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "Le Code ne correspond pas"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
msgid "Invalid email"
|
||||
msgstr "Courriel invalide"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Salut %(username)s,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
msgid ""
|
||||
"\n"
|
||||
" Email MFA code.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Code MFA envoyé par courriel.\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Si vous n'avez pas demandé ce code, veuillez ignorer ce courriel. Le code ci-dessus est valid pendant %(expires)s.\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "Bonjour %(username)s,"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
"Email MFA code\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Code MFA envoyé par e-mail\n"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
"If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Si vous n'avez pas demandé ce code, veuillez ignorer ce courriel. Le code ci-dessus est valid pendant %(expires)s.\n"
|
||||
|
||||
#: authentik/stages/authenticator_sms/models.py
|
||||
msgid ""
|
||||
"When enabled, the Phone number is only used during enrollment to verify the "
|
||||
@ -2778,11 +2906,6 @@ msgstr "Appareil SMS"
|
||||
msgid "SMS Devices"
|
||||
msgstr "Appareils SMS"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "Le Code ne correspond pas"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
msgid "Invalid phone number"
|
||||
msgstr "Numéro de téléphone invalide"
|
||||
@ -3021,14 +3144,6 @@ msgstr "Réinitialiser le Mot de Passe"
|
||||
msgid "Account Confirmation"
|
||||
msgstr "Confirmation du Compte"
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr ""
|
||||
"Si activé, les paramètres globaux de connexion courriel seront utilisés et "
|
||||
"les paramètres de connexion ci-dessous seront ignorés."
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Activate users upon completion of stage."
|
||||
msgstr "Activer les utilisateurs à la complétion de l'étape."
|
||||
@ -3045,10 +3160,6 @@ msgstr "Étape Email"
|
||||
msgid "Email Stages"
|
||||
msgstr "Étape Email"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr "Une erreur s'est produite lors de la modélisation du couriel"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Successfully verified Email."
|
||||
msgstr "Email vérifié avec succès."
|
||||
@ -3133,17 +3244,6 @@ msgstr ""
|
||||
"\n"
|
||||
"Cet email a été envoyé depuis le transport de notification %(name)s.\n"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Salut %(username)s,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
msgid ""
|
||||
"\n"
|
||||
@ -3165,11 +3265,6 @@ msgstr ""
|
||||
" Si vous n'avez pas requis de changement de mot de passe, veuillez ignorer cet e-mail. Le lien ci-dessus est valide pendant %(expires)s.\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "Bonjour %(username)s,"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
|
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-14 14:49+0000\n"
|
||||
"POT-Creation-Date: 2025-02-25 00:11+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2025\n"
|
||||
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
||||
@ -118,6 +118,10 @@ msgstr "用户没有访问此应用程序的权限。"
|
||||
msgid "Extra description not available"
|
||||
msgstr "额外描述不可用"
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr "无法设置组自身为父级。"
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
"When not set all providers are returned. When set to true, only backchannel "
|
||||
@ -162,6 +166,14 @@ msgstr "添加用户到组"
|
||||
msgid "Remove user from group"
|
||||
msgstr "从组中删除用户"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Enable superuser status"
|
||||
msgstr "启用超级用户状态"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Disable superuser status"
|
||||
msgstr "禁用超级用户状态"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "User's display name."
|
||||
msgstr "用户的显示名称。"
|
||||
@ -510,57 +522,6 @@ msgstr "Microsoft Entra 提供程序映射"
|
||||
msgid "Microsoft Entra Provider Mappings"
|
||||
msgstr "Microsoft Entra 提供程序映射"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
#: authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr "确定会话持续多长时间。默认值为 0 表示会话持续到浏览器关闭为止。(格式:hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr "启用时,连接令牌将会在断开连接时被删除。"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "已达到最大连接数。"
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(您已经在另一个标签页/窗口连接了)"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Signing Key"
|
||||
@ -662,6 +623,7 @@ msgid "Slack Webhook (Slack/Discord)"
|
||||
msgstr "Slack Webhook(Slack/Discord)"
|
||||
|
||||
#: authentik/events/models.py
|
||||
#: authentik/stages/authenticator_validate/models.py
|
||||
msgid "Email"
|
||||
msgstr "电子邮箱"
|
||||
|
||||
@ -1119,6 +1081,14 @@ msgstr "GeoIP:无法在城市数据库中找到客户端 IP。"
|
||||
msgid "Client IP is not in an allowed country."
|
||||
msgstr "客户端 IP 不在受允许的地区。"
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance from previous authentication is larger than threshold."
|
||||
msgstr "与上一次身份验证的距离超过阈值。"
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance is further than possible."
|
||||
msgstr "距离大幅超过可能值。"
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "GeoIP Policy"
|
||||
msgstr "GeoIP 策略"
|
||||
@ -1668,6 +1638,56 @@ msgstr "代理提供程序"
|
||||
msgid "Proxy Providers"
|
||||
msgstr "代理提供程序"
|
||||
|
||||
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr "确定会话持续多长时间。默认值为 0 表示会话持续到浏览器关闭为止。(格式:hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr "启用时,连接令牌将会在断开连接时被删除。"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "已达到最大连接数。"
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(您已经在另一个标签页/窗口连接了)"
|
||||
|
||||
#: authentik/providers/radius/models.py
|
||||
msgid "Shared secret between clients and server to hash packets."
|
||||
msgstr "用于哈希处理数据包的客户端服务端共享密钥。"
|
||||
@ -2521,6 +2541,109 @@ msgstr "Duo 设备"
|
||||
msgid "Duo Devices"
|
||||
msgstr "Duo 设备"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email OTP"
|
||||
msgstr "电子邮件 OTP"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr "启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
|
||||
msgstr "发出令牌有效的时间(格式:hours=3,minutes=17,seconds=300)。"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stage"
|
||||
msgstr "电子邮件身份验证器设置阶段"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stages"
|
||||
msgstr "电子邮件身份验证器设置阶段"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr "渲染电子邮件模板时发生异常"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Device"
|
||||
msgstr "电子邮件设备"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Devices"
|
||||
msgstr "电子邮件设备"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "代码不匹配"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
msgid "Invalid email"
|
||||
msgstr "无效电子邮件"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" %(username)s 您好,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
msgid ""
|
||||
"\n"
|
||||
" Email MFA code.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 电子邮件 MFA 代码。\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 如果您没有请求此代码,请忽略此电子邮件。上面的代码在 %(expires)s 内有效。\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "您好 %(username)s,"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
"Email MFA code\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"电子邮件 MFA 代码\n"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
"If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"如果您没有请求此代码,请忽略此电子邮件。上面的代码在 %(expires)s 内有效。\n"
|
||||
|
||||
#: authentik/stages/authenticator_sms/models.py
|
||||
msgid ""
|
||||
"When enabled, the Phone number is only used during enrollment to verify the "
|
||||
@ -2553,11 +2676,6 @@ msgstr "短信设备"
|
||||
msgid "SMS Devices"
|
||||
msgstr "短信设备"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "代码不匹配"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
msgid "Invalid phone number"
|
||||
msgstr "无效电话号码"
|
||||
@ -2780,12 +2898,6 @@ msgstr "密码重置"
|
||||
msgid "Account Confirmation"
|
||||
msgstr "账户确认"
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr "启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。"
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Activate users upon completion of stage."
|
||||
msgstr "完成阶段后激活用户。"
|
||||
@ -2802,10 +2914,6 @@ msgstr "电子邮件阶段"
|
||||
msgid "Email Stages"
|
||||
msgstr "电子邮件阶段"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr "渲染电子邮件模板时发生异常"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Successfully verified Email."
|
||||
msgstr "已成功验证电子邮件。"
|
||||
@ -2886,17 +2994,6 @@ msgstr ""
|
||||
"\n"
|
||||
"此邮件由通知递送 %(name)s 发送。\n"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" %(username)s 您好,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
msgid ""
|
||||
"\n"
|
||||
@ -2918,11 +3015,6 @@ msgstr ""
|
||||
" 如果您没有请求更改密码,请忽略此电子邮件。上面的链接在 %(expires)s 内有效。\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "您好 %(username)s,"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -14,7 +14,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-14 14:49+0000\n"
|
||||
"POT-Creation-Date: 2025-02-25 00:11+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2025\n"
|
||||
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
||||
@ -117,6 +117,10 @@ msgstr "用户没有访问此应用程序的权限。"
|
||||
msgid "Extra description not available"
|
||||
msgstr "额外描述不可用"
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr "无法设置组自身为父级。"
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
"When not set all providers are returned. When set to true, only backchannel "
|
||||
@ -161,6 +165,14 @@ msgstr "添加用户到组"
|
||||
msgid "Remove user from group"
|
||||
msgstr "从组中删除用户"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Enable superuser status"
|
||||
msgstr "启用超级用户状态"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Disable superuser status"
|
||||
msgstr "禁用超级用户状态"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "User's display name."
|
||||
msgstr "用户的显示名称。"
|
||||
@ -509,57 +521,6 @@ msgstr "Microsoft Entra 提供程序映射"
|
||||
msgid "Microsoft Entra Provider Mappings"
|
||||
msgstr "Microsoft Entra 提供程序映射"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
#: authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr "确定会话持续多长时间。默认值为 0 表示会话持续到浏览器关闭为止。(格式:hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr "启用时,连接令牌将会在断开连接时被删除。"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "已达到最大连接数。"
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(您已经在另一个标签页/窗口连接了)"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Signing Key"
|
||||
@ -661,6 +622,7 @@ msgid "Slack Webhook (Slack/Discord)"
|
||||
msgstr "Slack Webhook(Slack/Discord)"
|
||||
|
||||
#: authentik/events/models.py
|
||||
#: authentik/stages/authenticator_validate/models.py
|
||||
msgid "Email"
|
||||
msgstr "电子邮箱"
|
||||
|
||||
@ -1118,6 +1080,14 @@ msgstr "GeoIP:无法在城市数据库中找到客户端 IP。"
|
||||
msgid "Client IP is not in an allowed country."
|
||||
msgstr "客户端 IP 不在受允许的地区。"
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance from previous authentication is larger than threshold."
|
||||
msgstr "与上一次身份验证的距离超过阈值。"
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance is further than possible."
|
||||
msgstr "距离大幅超过可能值。"
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "GeoIP Policy"
|
||||
msgstr "GeoIP 策略"
|
||||
@ -1667,6 +1637,56 @@ msgstr "代理提供程序"
|
||||
msgid "Proxy Providers"
|
||||
msgstr "代理提供程序"
|
||||
|
||||
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr "确定会话持续多长时间。默认值为 0 表示会话持续到浏览器关闭为止。(格式:hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr "启用时,连接令牌将会在断开连接时被删除。"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "RAC 提供程序"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "RAC 端点"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "RAC 提供程序属性映射"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "RAC 连接令牌"
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "已达到最大连接数。"
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(您已经在另一个标签页/窗口连接了)"
|
||||
|
||||
#: authentik/providers/radius/models.py
|
||||
msgid "Shared secret between clients and server to hash packets."
|
||||
msgstr "在客户端和服务端之间共享密钥以哈希数据包。"
|
||||
@ -2520,6 +2540,109 @@ msgstr "Duo 设备"
|
||||
msgid "Duo Devices"
|
||||
msgstr "Duo 设备"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email OTP"
|
||||
msgstr "电子邮件 OTP"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr "启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
|
||||
msgstr "发出令牌有效的时间(格式:hours=3,minutes=17,seconds=300)。"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stage"
|
||||
msgstr "电子邮件身份验证器设置阶段"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stages"
|
||||
msgstr "电子邮件身份验证器设置阶段"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr "渲染电子邮件模板时发生异常"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Device"
|
||||
msgstr "电子邮件设备"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Devices"
|
||||
msgstr "电子邮件设备"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "代码不匹配"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
msgid "Invalid email"
|
||||
msgstr "无效电子邮件"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" %(username)s 您好,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
msgid ""
|
||||
"\n"
|
||||
" Email MFA code.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 电子邮件 MFA 代码。\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 如果您没有请求此代码,请忽略此电子邮件。上面的代码在 %(expires)s 内有效。\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "您好 %(username)s,"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
"Email MFA code\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"电子邮件 MFA 代码\n"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
"If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"如果您没有请求此代码,请忽略此电子邮件。上面的代码在 %(expires)s 内有效。\n"
|
||||
|
||||
#: authentik/stages/authenticator_sms/models.py
|
||||
msgid ""
|
||||
"When enabled, the Phone number is only used during enrollment to verify the "
|
||||
@ -2552,11 +2675,6 @@ msgstr "短信设备"
|
||||
msgid "SMS Devices"
|
||||
msgstr "短信设备"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "代码不匹配"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
msgid "Invalid phone number"
|
||||
msgstr "无效电话号码"
|
||||
@ -2779,12 +2897,6 @@ msgstr "密码重置"
|
||||
msgid "Account Confirmation"
|
||||
msgstr "账户确认"
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr "启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。"
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Activate users upon completion of stage."
|
||||
msgstr "完成阶段后激活用户。"
|
||||
@ -2801,10 +2913,6 @@ msgstr "电子邮件阶段"
|
||||
msgid "Email Stages"
|
||||
msgstr "电子邮件阶段"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr "渲染电子邮件模板时发生异常"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Successfully verified Email."
|
||||
msgstr "已成功验证电子邮件。"
|
||||
@ -2885,17 +2993,6 @@ msgstr ""
|
||||
"\n"
|
||||
"此邮件由通知递送 %(name)s 发送。\n"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" %(username)s 您好,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
msgid ""
|
||||
"\n"
|
||||
@ -2917,11 +3014,6 @@ msgstr ""
|
||||
" 如果您没有请求更改密码,请忽略此电子邮件。上面的链接在 %(expires)s 内有效。\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "您好 %(username)s,"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.2.4",
|
||||
"version": "2025.2.1",
|
||||
"private": true
|
||||
}
|
||||
|
646
poetry.lock
generated
646
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2025.2.4"
|
||||
version = "2025.2.1"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
@ -91,7 +91,7 @@ cryptography = "*"
|
||||
dacite = "*"
|
||||
deepmerge = "*"
|
||||
defusedxml = "*"
|
||||
django = "5.0.14"
|
||||
django = "*"
|
||||
django-countries = "*"
|
||||
django-cte = "*"
|
||||
django-filter = "*"
|
||||
@ -123,7 +123,7 @@ kubernetes = "*"
|
||||
ldap3 = "*"
|
||||
lxml = "*"
|
||||
msgraph-sdk = "*"
|
||||
opencontainers = { git = "https://github.com/BeryJu/oci-python", rev = "c791b19056769cd67957322806809ab70f5bead8", extras = ["reggie"] }
|
||||
opencontainers = { git = "https://github.com/vsoch/oci-python", rev = "20d69d9cc50a0fef31605b46f06da0c94f1ec3cf", extras = ["reggie"] }
|
||||
packaging = "*"
|
||||
paramiko = "*"
|
||||
psycopg = { extras = ["c"], version = "*" }
|
||||
|
122
schema.yml
122
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2025.2.4
|
||||
version: 2025.2.1
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
@ -39432,6 +39432,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-access-denied
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -39546,6 +39550,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-source-oauth-apple
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -39873,6 +39881,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-duo
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -40032,6 +40044,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-email
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -40288,6 +40304,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-sms
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -40451,6 +40471,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-static
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -40572,6 +40596,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-totp
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -40799,6 +40827,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-validate
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -40852,6 +40884,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-webauthn
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -41001,6 +41037,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-autosubmit
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -41264,6 +41304,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-captcha
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -41663,6 +41707,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-consent
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -42464,6 +42512,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-dummy
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -42666,6 +42718,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-email
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -43593,6 +43649,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-flow-error
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -43921,6 +43981,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: xak-flow-frame
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -44731,6 +44795,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-identification
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -46324,6 +46392,22 @@ components:
|
||||
- strict
|
||||
- regex
|
||||
type: string
|
||||
Message:
|
||||
type: object
|
||||
description: Base serializer class which doesn't implement create/update methods
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
level:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- level
|
||||
- message
|
||||
- tags
|
||||
Metadata:
|
||||
type: object
|
||||
description: Serializer for blueprint metadata
|
||||
@ -47209,6 +47293,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-provider-oauth2-device-code
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -47237,6 +47325,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-provider-oauth2-device-code-finish
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -49387,6 +49479,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-password
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -52942,6 +53038,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-source-plex
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -53467,6 +53567,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-prompt
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -54663,6 +54767,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: xak-flow-redirect
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -56528,6 +56636,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-session-end
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -56662,6 +56774,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: xak-flow-shell
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@ -57943,6 +58059,10 @@ components:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-user-login
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
|
@ -4,7 +4,7 @@ This package provides a generated API Client for [authentik](https://goauthentik
|
||||
|
||||
### Building
|
||||
|
||||
See https://docs.goauthentik.io/docs/developer-docs/making-schema-changes
|
||||
See https://docs.goauthentik.io/docs/developer-docs/api/making-schema-changes#building-the-web-client
|
||||
|
||||
### Consuming
|
||||
|
||||
|
141
web/build-observer-plugin.mjs
Normal file
141
web/build-observer-plugin.mjs
Normal file
@ -0,0 +1,141 @@
|
||||
import * as http from "http";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* Serializes a custom event to a text stream.
|
||||
* a
|
||||
* @param {Event} event
|
||||
* @returns {string}
|
||||
*/
|
||||
export function serializeCustomEventToStream(event) {
|
||||
// @ts-ignore
|
||||
const data = event.detail ?? {};
|
||||
|
||||
const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`];
|
||||
|
||||
return eventContent.join("\n") + "\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the build observer plugin.
|
||||
*
|
||||
* @typedef {Object} BuildObserverOptions
|
||||
*
|
||||
* @property {URL} serverURL
|
||||
* @property {string} logPrefix
|
||||
* @property {string} relativeRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a plugin that listens for build events and sends them to a server-sent event stream.
|
||||
*
|
||||
* @param {BuildObserverOptions} options
|
||||
* @returns {import('esbuild').Plugin}
|
||||
*/
|
||||
export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) {
|
||||
const timerLabel = `[${logPrefix}] Build`;
|
||||
const endpoint = serverURL.pathname;
|
||||
const dispatcher = new EventTarget();
|
||||
|
||||
const eventServer = http.createServer((req, res) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
|
||||
if (req.url !== endpoint) {
|
||||
console.log(`🚫 Invalid request to ${req.url}`);
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🔌 Client connected");
|
||||
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
const listener = (event) => {
|
||||
const body = serializeCustomEventToStream(event);
|
||||
|
||||
res.write(body);
|
||||
};
|
||||
|
||||
dispatcher.addEventListener("esbuild:start", listener);
|
||||
dispatcher.addEventListener("esbuild:error", listener);
|
||||
dispatcher.addEventListener("esbuild:end", listener);
|
||||
|
||||
req.on("close", () => {
|
||||
console.log("🔌 Client disconnected");
|
||||
|
||||
clearInterval(keepAliveInterval);
|
||||
|
||||
dispatcher.removeEventListener("esbuild:start", listener);
|
||||
dispatcher.removeEventListener("esbuild:error", listener);
|
||||
dispatcher.removeEventListener("esbuild:end", listener);
|
||||
});
|
||||
|
||||
const keepAliveInterval = setInterval(() => {
|
||||
console.timeStamp("🏓 Keep-alive");
|
||||
|
||||
res.write("event: keep-alive\n\n");
|
||||
res.write(serializeCustomEventToStream(new CustomEvent("esbuild:keep-alive")));
|
||||
}, 15_000);
|
||||
});
|
||||
|
||||
return {
|
||||
name: "build-watcher",
|
||||
setup: (build) => {
|
||||
eventServer.listen(parseInt(serverURL.port, 10), serverURL.hostname);
|
||||
|
||||
build.onDispose(() => {
|
||||
eventServer.close();
|
||||
});
|
||||
|
||||
build.onStart(() => {
|
||||
console.time(timerLabel);
|
||||
|
||||
dispatcher.dispatchEvent(
|
||||
new CustomEvent("esbuild:start", {
|
||||
detail: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
build.onEnd((buildResult) => {
|
||||
console.timeEnd(timerLabel);
|
||||
|
||||
if (!buildResult.errors.length) {
|
||||
dispatcher.dispatchEvent(
|
||||
new CustomEvent("esbuild:end", {
|
||||
detail: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn(`Build ended with ${buildResult.errors.length} errors`);
|
||||
|
||||
dispatcher.dispatchEvent(
|
||||
new CustomEvent("esbuild:error", {
|
||||
detail: buildResult.errors.map((error) => ({
|
||||
...error,
|
||||
location: error.location
|
||||
? {
|
||||
...error.location,
|
||||
file: path.resolve(relativeRoot, error.location.file),
|
||||
}
|
||||
: null,
|
||||
})),
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
295
web/build.mjs
295
web/build.mjs
@ -1,45 +1,54 @@
|
||||
import { execFileSync } from "child_process";
|
||||
import * as chokidar from "chokidar";
|
||||
import esbuild from "esbuild";
|
||||
import fs from "fs";
|
||||
import findFreePorts from "find-free-ports";
|
||||
import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs";
|
||||
import { globSync } from "glob";
|
||||
import path from "path";
|
||||
import { cwd } from "process";
|
||||
import process from "process";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
import { buildObserverPlugin } from "./build-observer-plugin.mjs";
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
let authentikProjectRoot = __dirname + "../";
|
||||
|
||||
try {
|
||||
// Use the package.json file in the root folder, as it has the current version information.
|
||||
authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
encoding: "utf8",
|
||||
}).replace("\n", "");
|
||||
} catch (_exc) {
|
||||
// We probably don't have a .git folder, which could happen in container builds
|
||||
} catch (_error) {
|
||||
// We probably don't have a .git folder, which could happen in container builds.
|
||||
}
|
||||
const rootPackage = JSON.parse(fs.readFileSync(path.join(authentikProjectRoot, "./package.json")));
|
||||
|
||||
const isProdBuild = process.env.NODE_ENV === "production";
|
||||
const packageJSONPath = path.join(authentikProjectRoot, "./package.json");
|
||||
const rootPackage = JSON.parse(readFileSync(packageJSONPath, "utf8"));
|
||||
|
||||
const apiBasePath = process.env.AK_API_BASE_PATH || "";
|
||||
const NODE_ENV = process.env.NODE_ENV || "development";
|
||||
const AK_API_BASE_PATH = process.env.AK_API_BASE_PATH || "";
|
||||
|
||||
const envGitHashKey = "GIT_BUILD_HASH";
|
||||
const environmentVars = new Map([
|
||||
["NODE_ENV", NODE_ENV],
|
||||
["CWD", cwd()],
|
||||
["AK_API_BASE_PATH", AK_API_BASE_PATH],
|
||||
]);
|
||||
|
||||
const definitions = {
|
||||
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
|
||||
"process.env.CWD": JSON.stringify(cwd()),
|
||||
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
|
||||
};
|
||||
const definitions = Object.fromEntries(
|
||||
Array.from(environmentVars).map(([key, value]) => {
|
||||
return [`process.env.${key}`, JSON.stringify(value)];
|
||||
}),
|
||||
);
|
||||
|
||||
// All is magic is just to make sure the assets are copied into the right places. This is a very
|
||||
// stripped down version of what the rollup-copy-plugin does, without any of the features we don't
|
||||
// use, and using globSync instead of globby since we already had globSync lying around thanks to
|
||||
// Typescript. If there's a third argument in an array entry, it's used to replace the internal path
|
||||
// before concatenating it all together as the destination target.
|
||||
|
||||
const otherFiles = [
|
||||
/**
|
||||
* All is magic is just to make sure the assets are copied into the right places. This is a very
|
||||
* stripped down version of what the rollup-copy-plugin does, without any of the features we don't
|
||||
* use, and using globSync instead of globby since we already had globSync lying around thanks to
|
||||
* Typescript. If there's a third argument in an array entry, it's used to replace the internal path
|
||||
* before concatenating it all together as the destination target.
|
||||
* @type {Array<[string, string, string?]>}
|
||||
*/
|
||||
const assetsFileMappings = [
|
||||
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
|
||||
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
|
||||
["src/custom.css", "."],
|
||||
@ -48,28 +57,47 @@ const otherFiles = [
|
||||
["./icons/*", "./assets/icons"],
|
||||
];
|
||||
|
||||
const isFile = (filePath) => fs.statSync(filePath).isFile();
|
||||
/**
|
||||
* @param {string} filePath
|
||||
*/
|
||||
const isFile = (filePath) => statSync(filePath).isFile();
|
||||
|
||||
/**
|
||||
* @param {string} src Source file
|
||||
* @param {string} dest Destination folder
|
||||
* @param {string} [strip] Path to strip from the source file
|
||||
*/
|
||||
function nameCopyTarget(src, dest, strip) {
|
||||
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
|
||||
return [src, target];
|
||||
}
|
||||
|
||||
for (const [source, rawdest, strip] of otherFiles) {
|
||||
for (const [source, rawdest, strip] of assetsFileMappings) {
|
||||
const matchedPaths = globSync(source);
|
||||
const dest = path.join("dist", rawdest);
|
||||
|
||||
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
|
||||
|
||||
for (const [src, dest] of copyTargets) {
|
||||
if (isFile(src)) {
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fs.copyFileSync(src, dest);
|
||||
mkdirSync(path.dirname(dest), { recursive: true });
|
||||
copyFileSync(src, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This starts the definitions used for esbuild: Our targets, our arguments, the function for
|
||||
// running a build, and three options for building: watching, building, and building the proxy.
|
||||
// Ordered by largest to smallest interface to build even faster
|
||||
const interfaces = [
|
||||
/**
|
||||
* @typedef {[source: string, destination: string]} EntryPoint
|
||||
*/
|
||||
|
||||
/**
|
||||
* This starts the definitions used for esbuild: Our targets, our arguments, the function for
|
||||
* running a build, and three options for building: watching, building, and building the proxy.
|
||||
* Ordered by largest to smallest interface to build even faster
|
||||
*
|
||||
* @type {EntryPoint[]}
|
||||
*/
|
||||
const entryPoints = [
|
||||
["admin/AdminInterface/AdminInterface.ts", "admin"],
|
||||
["user/UserInterface.ts", "user"],
|
||||
["flow/FlowInterface.ts", "flow"],
|
||||
@ -79,18 +107,26 @@ const interfaces = [
|
||||
["polyfill/poly.ts", "."],
|
||||
];
|
||||
|
||||
const baseArgs = {
|
||||
/**
|
||||
* @satisfies {import("esbuild").BuildOptions}
|
||||
*/
|
||||
const BASE_ESBUILD_OPTIONS = {
|
||||
bundle: true,
|
||||
write: true,
|
||||
sourcemap: true,
|
||||
minify: isProdBuild,
|
||||
minify: NODE_ENV === "production",
|
||||
splitting: true,
|
||||
treeShaking: true,
|
||||
external: ["*.woff", "*.woff2"],
|
||||
tsconfig: "./tsconfig.json",
|
||||
loader: { ".css": "text", ".md": "text" },
|
||||
loader: {
|
||||
".css": "text",
|
||||
".md": "text",
|
||||
".mdx": "text",
|
||||
},
|
||||
define: definitions,
|
||||
format: "esm",
|
||||
plugins: [],
|
||||
logOverride: {
|
||||
/**
|
||||
* HACK: Silences issue originating in ESBuild.
|
||||
@ -102,91 +138,144 @@ const baseArgs = {
|
||||
},
|
||||
};
|
||||
|
||||
function getVersion() {
|
||||
let version = rootPackage.version;
|
||||
if (process.env[envGitHashKey]) {
|
||||
version = `${version}+${process.env[envGitHashKey]}`;
|
||||
/**
|
||||
* Creates a version ID for the build.
|
||||
* @returns {string}
|
||||
*/
|
||||
function composeVersionID() {
|
||||
const { version } = rootPackage;
|
||||
const buildHash = process.env.GIT_BUILD_HASH;
|
||||
|
||||
if (buildHash) {
|
||||
return `${version}+${buildHash}`;
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
async function buildOneSource(source, dest) {
|
||||
const DIST = path.join(__dirname, "./dist", dest);
|
||||
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
|
||||
/**
|
||||
* Build a single entry point.
|
||||
*
|
||||
* @param {EntryPoint} buildTarget
|
||||
* @param {Partial<esbuild.BuildOptions>} [overrides]
|
||||
* @throws {Error} on build failure
|
||||
*/
|
||||
function createEntryPointOptions([source, dest], overrides = {}) {
|
||||
const outdir = path.join(__dirname, "./dist", dest);
|
||||
|
||||
try {
|
||||
const start = Date.now();
|
||||
await esbuild.build({
|
||||
...baseArgs,
|
||||
entryPoints: [`./src/${source}`],
|
||||
entryNames: `[dir]/[name]-${getVersion()}`,
|
||||
outdir: DIST,
|
||||
});
|
||||
const end = Date.now();
|
||||
console.log(
|
||||
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${
|
||||
Date.now() - start
|
||||
}ms`,
|
||||
);
|
||||
return 0;
|
||||
} catch (exc) {
|
||||
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
|
||||
return 1;
|
||||
}
|
||||
return {
|
||||
...BASE_ESBUILD_OPTIONS,
|
||||
entryPoints: [`./src/${source}`],
|
||||
entryNames: `[dir]/[name]-${composeVersionID()}`,
|
||||
outdir,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
async function buildAuthentik(interfaces) {
|
||||
const code = await Promise.allSettled(
|
||||
interfaces.map(([source, dest]) => buildOneSource(source, dest)),
|
||||
/**
|
||||
* Build all entry points in parallel.
|
||||
*
|
||||
* @param {EntryPoint[]} entryPoints
|
||||
*/
|
||||
async function buildParallel(entryPoints) {
|
||||
await Promise.allSettled(
|
||||
entryPoints.map((entryPoint) => {
|
||||
return esbuild.build(createEntryPointOptions(entryPoint));
|
||||
}),
|
||||
);
|
||||
const finalCode = code.reduce((a, res) => a + res.value, 0);
|
||||
if (finalCode > 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
let timeoutId = null;
|
||||
function debouncedBuild() {
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
console.clear();
|
||||
buildAuthentik(interfaces);
|
||||
}, 250);
|
||||
}
|
||||
function doHelp() {
|
||||
console.log(`Build the authentik UI
|
||||
|
||||
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
|
||||
console.log(`Build the authentikUI
|
||||
|
||||
options:
|
||||
-w, --watch: Build all ${interfaces.length} interfaces
|
||||
-p, --proxy: Build only the polyfills and the loading application
|
||||
-h, --help: This help message
|
||||
options:
|
||||
-w, --watch: Build all ${entryPoints.length} interfaces
|
||||
-p, --proxy: Build only the polyfills and the loading application
|
||||
-h, --help: This help message
|
||||
`);
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
|
||||
console.log("Watching ./src for changes");
|
||||
chokidar.watch("./src").on("all", (event, path) => {
|
||||
if (!["add", "change", "unlink"].includes(event)) {
|
||||
return;
|
||||
}
|
||||
if (!/(\.css|\.ts|\.js)$/.test(path)) {
|
||||
return;
|
||||
}
|
||||
debouncedBuild();
|
||||
});
|
||||
} else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) {
|
||||
// There's no watch-for-proxy, sorry.
|
||||
process.exit(
|
||||
await buildAuthentik(
|
||||
interfaces.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
|
||||
),
|
||||
async function doWatch() {
|
||||
console.log("Watching all entry points...");
|
||||
|
||||
const wathcherPorts = await findFreePorts(entryPoints.length);
|
||||
|
||||
const buildContexts = await Promise.all(
|
||||
entryPoints.map((entryPoint, i) => {
|
||||
const port = wathcherPorts[i];
|
||||
const serverURL = new URL(`http://localhost:${port}/events`);
|
||||
|
||||
return esbuild.context(
|
||||
createEntryPointOptions(entryPoint, {
|
||||
plugins: [
|
||||
...BASE_ESBUILD_OPTIONS.plugins,
|
||||
buildObserverPlugin({
|
||||
serverURL,
|
||||
logPrefix: entryPoint[1],
|
||||
relativeRoot: __dirname,
|
||||
}),
|
||||
],
|
||||
define: {
|
||||
...definitions,
|
||||
"process.env.WATCHER_URL": JSON.stringify(serverURL.toString()),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(buildContexts.map((context) => context.rebuild()));
|
||||
|
||||
await Promise.allSettled(buildContexts.map((context) => context.watch()));
|
||||
|
||||
return /** @type {Promise<void>} */ (
|
||||
new Promise((resolve) => {
|
||||
process.on("SIGINT", () => {
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// And the fallback: just build it.
|
||||
process.exit(await buildAuthentik(interfaces));
|
||||
}
|
||||
|
||||
async function doBuild() {
|
||||
console.log("Building all entry points");
|
||||
|
||||
return buildParallel(entryPoints);
|
||||
}
|
||||
|
||||
async function doProxy() {
|
||||
return buildParallel(
|
||||
entryPoints.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
|
||||
);
|
||||
}
|
||||
|
||||
async function delegateCommand() {
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case "-h":
|
||||
case "--help":
|
||||
return doHelp();
|
||||
case "-w":
|
||||
case "--watch":
|
||||
return doWatch();
|
||||
// There's no watch-for-proxy, sorry.
|
||||
case "-p":
|
||||
case "--proxy":
|
||||
return doProxy();
|
||||
default:
|
||||
return doBuild();
|
||||
}
|
||||
}
|
||||
|
||||
await delegateCommand()
|
||||
.then(() => {
|
||||
console.log("Build complete");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
16
web/package-lock.json
generated
16
web/package-lock.json
generated
@ -23,7 +23,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2024.12.3-1739965710",
|
||||
"@goauthentik/api": "^2025.2.1-1740653734",
|
||||
"@lit-labs/ssr": "^3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
@ -89,6 +89,7 @@
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-plugin-lit": "^1.15.0",
|
||||
"eslint-plugin-wc": "^2.1.1",
|
||||
"find-free-ports": "^3.1.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"globals": "^15.10.0",
|
||||
@ -1814,9 +1815,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2024.12.3-1739965710",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.12.3-1739965710.tgz",
|
||||
"integrity": "sha512-16zoQWeJhAFSwttvqLRoXoQA43tMW1ZXDEihW6r8rtWtlxqPh7n36RtcWYraYiLcjmJskI90zdgz6k1kmY5AXw=="
|
||||
"version": "2025.2.1-1740653734",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.2.1-1740653734.tgz",
|
||||
"integrity": "sha512-GRxBt52lgZOvEu7l9DN1lj0L2Q9KUiftrC9MWfaz3dIlw1s+kKzic/NTTlB7AaEsRqw7+i10aI6GkiKAErw2VA=="
|
||||
},
|
||||
"node_modules/@goauthentik/web": {
|
||||
"resolved": "",
|
||||
@ -12892,6 +12893,13 @@
|
||||
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/find-free-ports": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/find-free-ports/-/find-free-ports-3.1.1.tgz",
|
||||
"integrity": "sha512-hQebewth9i5qkf0a0u06iFaxQssk5ZnPBBggsa1vk8zCYaZoz9IZXpoRLTbEOrYdqfrjvcxU00gYoCPgmXugKA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
|
@ -11,7 +11,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2024.12.3-1739965710",
|
||||
"@goauthentik/api": "^2025.2.1-1740653734",
|
||||
"@lit-labs/ssr": "^3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
@ -77,6 +77,7 @@
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-plugin-lit": "^1.15.0",
|
||||
"eslint-plugin-wc": "^2.1.1",
|
||||
"find-free-ports": "^3.1.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"globals": "^15.10.0",
|
||||
|
@ -90,12 +90,14 @@ export class AdminInterface extends AuthenticatedInterface {
|
||||
constructor() {
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
|
||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
||||
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
||||
updateURLParams({
|
||||
notificationDrawerOpen: this.notificationDrawerOpen,
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => {
|
||||
this.apiDrawerOpen = !this.apiDrawerOpen;
|
||||
updateURLParams({
|
||||
@ -107,6 +109,7 @@ export class AdminInterface extends AuthenticatedInterface {
|
||||
async firstUpdated(): Promise<void> {
|
||||
configureSentry(true);
|
||||
this.user = await me();
|
||||
|
||||
const canAccessAdmin =
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
@ -116,6 +119,16 @@ export class AdminInterface extends AuthenticatedInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async connectedCallback(): Promise<void> {
|
||||
super.connectedCallback();
|
||||
|
||||
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
|
||||
const { ESBuildObserver } = await import("@goauthentik/common/client");
|
||||
|
||||
new ESBuildObserver(process.env.WATCHER_URL);
|
||||
}
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
const sidebarClasses = {
|
||||
"pf-m-light": this.activeTheme === UiThemeEnum.Light,
|
||||
|
@ -94,7 +94,7 @@ export class ApplicationEntitlementsPage extends Table<ApplicationEntitlement> {
|
||||
}
|
||||
|
||||
renderExpanded(item: ApplicationEntitlement): TemplateResult {
|
||||
return html`<td></td>
|
||||
return html` <td></td>
|
||||
<td role="cell" colspan="4">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
<div class="pf-c-content">
|
||||
|
@ -58,7 +58,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
get bindingsAsColumns() {
|
||||
return this.wizard.bindings.map((binding, index) => {
|
||||
const { order, enabled, timeout } = binding;
|
||||
const isSet = P.union(P.string.minLength(1), P.number);
|
||||
const isSet = P.string.minLength(1);
|
||||
const policy = match(binding)
|
||||
.with({ policy: isSet }, (v) => msg(str`Policy ${v.policyObj?.name}`))
|
||||
.with({ group: isSet }, (v) => msg(str`Group ${v.groupObj?.name}`))
|
||||
|
@ -21,22 +21,12 @@ export class RelatedApplicationButton extends AKElement {
|
||||
@property({ attribute: false })
|
||||
provider?: Provider;
|
||||
|
||||
@property()
|
||||
mode: "primary" | "backchannel" = "primary";
|
||||
|
||||
render(): TemplateResult {
|
||||
if (this.mode === "primary" && this.provider?.assignedApplicationSlug) {
|
||||
if (this.provider?.assignedApplicationSlug) {
|
||||
return html`<a href="#/core/applications/${this.provider.assignedApplicationSlug}">
|
||||
${this.provider.assignedApplicationName}
|
||||
</a>`;
|
||||
}
|
||||
if (this.mode === "backchannel" && this.provider?.assignedBackchannelApplicationSlug) {
|
||||
return html`<a
|
||||
href="#/core/applications/${this.provider.assignedBackchannelApplicationSlug}"
|
||||
>
|
||||
${this.provider.assignedBackchannelApplicationName}
|
||||
</a>`;
|
||||
}
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit"> ${msg("Create")} </span>
|
||||
<span slot="header"> ${msg("Create Application")} </span>
|
||||
|
@ -7,10 +7,10 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import "@goauthentik/components/events/ObjectChangelog";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/Markdown";
|
||||
import "@goauthentik/elements/SyncStatusCard";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
|
||||
|
@ -4,7 +4,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import renderDescriptionList from "@goauthentik/components/DescriptionList";
|
||||
import "@goauthentik/components/events/ObjectChangelog";
|
||||
import MDProviderOAuth2 from "@goauthentik/docs/add-secure-apps/providers/oauth2/index.md";
|
||||
import MDProviderOAuth2 from "@goauthentik/docs/add-secure-apps/providers/oauth2/index.mdx";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
|
@ -13,7 +13,7 @@ import MDNginxStandalone from "@goauthentik/docs/add-secure-apps/providers/proxy
|
||||
import MDTraefikCompose from "@goauthentik/docs/add-secure-apps/providers/proxy/_traefik_compose.md";
|
||||
import MDTraefikIngress from "@goauthentik/docs/add-secure-apps/providers/proxy/_traefik_ingress.md";
|
||||
import MDTraefikStandalone from "@goauthentik/docs/add-secure-apps/providers/proxy/_traefik_standalone.md";
|
||||
import MDHeaderAuthentication from "@goauthentik/docs/add-secure-apps/providers/proxy/header_authentication.md";
|
||||
import MDHeaderAuthentication from "@goauthentik/docs/add-secure-apps/providers/proxy/header_authentication.mdx";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/Markdown";
|
||||
@ -118,7 +118,7 @@ export class ProxyProviderViewPage extends AKElement {
|
||||
}
|
||||
|
||||
renderConfig(): TemplateResult {
|
||||
const serves = [
|
||||
const servers = [
|
||||
{
|
||||
label: msg("Nginx (Ingress)"),
|
||||
md: MDNginxIngress,
|
||||
@ -184,7 +184,7 @@ export class ProxyProviderViewPage extends AKElement {
|
||||
},
|
||||
];
|
||||
return html`<ak-tabs pageIdentifier="proxy-setup">
|
||||
${serves.map((server) => {
|
||||
${servers.map((server) => {
|
||||
return html`<section
|
||||
slot="page-${convertToSlug(server.label)}"
|
||||
data-tab-title="${server.label}"
|
||||
|
@ -9,10 +9,10 @@ import "@goauthentik/components/events/ObjectChangelog";
|
||||
import MDSCIMProvider from "@goauthentik/docs/add-secure-apps/providers/scim/index.md";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/Markdown";
|
||||
import "@goauthentik/elements/SyncStatusCard";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
|
||||
@ -173,7 +173,6 @@ export class SCIMProviderViewPage extends AKElement {
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-provider-related-application
|
||||
mode="backchannel"
|
||||
.provider=${this.provider}
|
||||
></ak-provider-related-application>
|
||||
</div>
|
||||
|
@ -8,11 +8,11 @@ import MDSourceKerberosBrowser from "@goauthentik/docs/users-sources/sources/pro
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/Markdown";
|
||||
import "@goauthentik/elements/SyncStatusCard";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
|
@ -6,11 +6,11 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import "@goauthentik/components/events/ObjectChangelog";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/SyncStatusCard";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
|
170
web/src/common/client.ts
Normal file
170
web/src/common/client.ts
Normal file
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @file
|
||||
* Client-side observer for ESBuild events.
|
||||
*/
|
||||
import type { Message as ESBuildMessage } from "esbuild";
|
||||
|
||||
const logPrefix = "👷 [ESBuild]";
|
||||
const log = console.debug.bind(console, logPrefix);
|
||||
|
||||
type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void;
|
||||
|
||||
/**
|
||||
* A client-side watcher for ESBuild.
|
||||
*
|
||||
* Note that this should be conditionally imported in your code, so that
|
||||
* ESBuild may tree-shake it out of production builds.
|
||||
*
|
||||
* ```ts
|
||||
* if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
|
||||
* const { ESBuildObserver } = await import("@goauthentik/common/client");
|
||||
*
|
||||
* new ESBuildObserver(process.env.WATCHER_URL);
|
||||
* }
|
||||
* ```
|
||||
}
|
||||
|
||||
*/
|
||||
export class ESBuildObserver extends EventSource {
|
||||
/**
|
||||
* Whether the watcher has a recent connection to the server.
|
||||
*/
|
||||
alive = true;
|
||||
|
||||
/**
|
||||
* The number of errors that have occurred since the watcher started.
|
||||
*/
|
||||
errorCount = 0;
|
||||
|
||||
/**
|
||||
* Whether a reload has been requested while offline.
|
||||
*/
|
||||
deferredReload = false;
|
||||
|
||||
/**
|
||||
* The last time a message was received from the server.
|
||||
*/
|
||||
lastUpdatedAt = Date.now();
|
||||
|
||||
/**
|
||||
* Whether the browser considers itself online.
|
||||
*/
|
||||
online = true;
|
||||
|
||||
/**
|
||||
* The ID of the animation frame for the reload.
|
||||
*/
|
||||
#reloadFrameID = -1;
|
||||
|
||||
/**
|
||||
* The interval for the keep-alive check.
|
||||
*/
|
||||
#keepAliveInterval: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
#trackActivity = () => {
|
||||
this.lastUpdatedAt = Date.now();
|
||||
this.alive = true;
|
||||
};
|
||||
|
||||
#startListener: BuildEventListener = () => {
|
||||
this.#trackActivity();
|
||||
log("⏰ Build started...");
|
||||
};
|
||||
|
||||
#internalErrorListener = () => {
|
||||
this.errorCount += 1;
|
||||
|
||||
if (this.errorCount > 100) {
|
||||
clearTimeout(this.#keepAliveInterval);
|
||||
|
||||
this.close();
|
||||
log("⛔️ Closing connection");
|
||||
}
|
||||
};
|
||||
|
||||
#errorListener: BuildEventListener<string> = (event) => {
|
||||
this.#trackActivity();
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.group(logPrefix, "⛔️⛔️⛔️ Build error...");
|
||||
|
||||
const esbuildErrorMessages: ESBuildMessage[] = JSON.parse(event.data);
|
||||
|
||||
for (const error of esbuildErrorMessages) {
|
||||
console.warn(error.text);
|
||||
|
||||
if (error.location) {
|
||||
console.debug(
|
||||
`file://${error.location.file}:${error.location.line}:${error.location.column}`,
|
||||
);
|
||||
console.debug(error.location.lineText);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.groupEnd();
|
||||
};
|
||||
|
||||
#endListener: BuildEventListener = () => {
|
||||
cancelAnimationFrame(this.#reloadFrameID);
|
||||
|
||||
this.#trackActivity();
|
||||
|
||||
if (!this.online) {
|
||||
log("🚫 Build finished while offline.");
|
||||
this.deferredReload = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log("🛎️ Build completed! Reloading...");
|
||||
|
||||
// We use an animation frame to keep the reload from happening before the
|
||||
// event loop has a chance to process the message.
|
||||
this.#reloadFrameID = requestAnimationFrame(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
#keepAliveListener: BuildEventListener = () => {
|
||||
this.#trackActivity();
|
||||
log("🏓 Keep-alive");
|
||||
};
|
||||
|
||||
constructor(url: string | URL) {
|
||||
super(url);
|
||||
|
||||
this.addEventListener("esbuild:start", this.#startListener);
|
||||
this.addEventListener("esbuild:end", this.#endListener);
|
||||
this.addEventListener("esbuild:error", this.#errorListener);
|
||||
this.addEventListener("esbuild:keep-alive", this.#keepAliveListener);
|
||||
|
||||
this.addEventListener("error", this.#internalErrorListener);
|
||||
|
||||
window.addEventListener("offline", () => {
|
||||
this.online = false;
|
||||
});
|
||||
|
||||
window.addEventListener("online", () => {
|
||||
this.online = true;
|
||||
|
||||
if (!this.deferredReload) return;
|
||||
|
||||
log("🛎️ Reloading after offline build...");
|
||||
this.deferredReload = false;
|
||||
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
log("🛎️ Listening for build changes...");
|
||||
|
||||
this.#keepAliveInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
|
||||
if (now - this.lastUpdatedAt < 10_000) return;
|
||||
|
||||
this.alive = false;
|
||||
log("👋 Waiting for build to start...");
|
||||
}, 15_000);
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2025.2.4";
|
||||
export const VERSION = "2025.2.1";
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
@ -3,92 +3,17 @@ import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-status-label";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/events/LogViewer";
|
||||
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFTable from "@patternfly/patternfly/components/Table/table.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { SyncStatus, SystemTask, SystemTaskStatusEnum } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-sync-status-table")
|
||||
export class SyncStatusTable extends Table<SystemTask> {
|
||||
@property({ attribute: false })
|
||||
tasks: SystemTask[] = [];
|
||||
|
||||
expandable = true;
|
||||
|
||||
static get styles() {
|
||||
return super.styles.concat(css`
|
||||
code:not(:last-of-type)::after {
|
||||
content: "-";
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<SystemTask>> {
|
||||
return {
|
||||
pagination: {
|
||||
next: 0,
|
||||
previous: 0,
|
||||
count: this.tasks.length,
|
||||
current: 1,
|
||||
totalPages: 1,
|
||||
startIndex: 0,
|
||||
endIndex: this.tasks.length,
|
||||
},
|
||||
results: this.tasks,
|
||||
};
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(msg("Task")),
|
||||
new TableColumn(msg("Status")),
|
||||
new TableColumn(msg("Finished")),
|
||||
];
|
||||
}
|
||||
|
||||
row(item: SystemTask): TemplateResult[] {
|
||||
const nameParts = item.fullName.split(":");
|
||||
nameParts.shift();
|
||||
return [
|
||||
html`<div>${item.name}</div>
|
||||
<small>${nameParts.map((part) => html`<code>${part}</code>`)}</small>`,
|
||||
html`<ak-status-label
|
||||
?good=${item.status === SystemTaskStatusEnum.Successful}
|
||||
good-label=${msg("Finished successfully")}
|
||||
bad-label=${msg("Finished with errors")}
|
||||
></ak-status-label>`,
|
||||
html`<div>${getRelativeTime(item.finishTimestamp)}</div>
|
||||
<small>${item.finishTimestamp.toLocaleString()}</small>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: SystemTask): TemplateResult {
|
||||
return html`<td role="cell" colspan="4">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
<ak-log-viewer .logs=${item?.messages}></ak-log-viewer>
|
||||
</div>
|
||||
</td>`;
|
||||
}
|
||||
|
||||
renderToolbarContainer() {
|
||||
return html``;
|
||||
}
|
||||
|
||||
renderTablePagination() {
|
||||
return html``;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-sync-status-card")
|
||||
export class SyncStatusCard extends AKElement {
|
||||
@state()
|
||||
@ -104,7 +29,7 @@ export class SyncStatusCard extends AKElement {
|
||||
triggerSync!: () => Promise<unknown>;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFCard, PFTable];
|
||||
return [PFBase, PFCard];
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
@ -115,6 +40,25 @@ export class SyncStatusCard extends AKElement {
|
||||
});
|
||||
}
|
||||
|
||||
renderSyncTask(task: SystemTask): TemplateResult {
|
||||
return html`<li>
|
||||
${(this.syncState?.tasks || []).length > 1 ? html`<span>${task.name}</span>` : nothing}
|
||||
<span
|
||||
><ak-status-label
|
||||
?good=${task.status === SystemTaskStatusEnum.Successful}
|
||||
good-label=${msg("Finished successfully")}
|
||||
bad-label=${msg("Finished with errors")}
|
||||
></ak-status-label
|
||||
></span>
|
||||
<span
|
||||
>${msg(
|
||||
str`Finished ${getRelativeTime(task.finishTimestamp)} (${task.finishTimestamp.toLocaleString()})`,
|
||||
)}</span
|
||||
>
|
||||
<ak-log-viewer .logs=${task?.messages}></ak-log-viewer>
|
||||
</li> `;
|
||||
}
|
||||
|
||||
renderSyncStatus(): TemplateResult {
|
||||
if (this.loading) {
|
||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||
@ -128,7 +72,13 @@ export class SyncStatusCard extends AKElement {
|
||||
if (this.syncState.tasks.length < 1) {
|
||||
return html`${msg("Not synced yet.")}`;
|
||||
}
|
||||
return html`<ak-sync-status-table .tasks=${this.syncState.tasks}></ak-sync-status-table>`;
|
||||
return html`
|
||||
<ul class="pf-c-list">
|
||||
${this.syncState.tasks.map((task) => {
|
||||
return this.renderSyncTask(task);
|
||||
})}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@ -170,7 +120,6 @@ export class SyncStatusCard extends AKElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-sync-status-table": SyncStatusTable;
|
||||
"ak-sync-status-card": SyncStatusCard;
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import { LogLevelEnum, SyncStatus, SystemTaskStatusEnum } from "@goauthentik/api";
|
||||
|
||||
import "./SyncStatusCard";
|
||||
|
||||
const metadata: Meta<SyncStatus> = {
|
||||
title: "Elements/<ak-sync-status-card>",
|
||||
component: "ak-sync-status-card",
|
||||
};
|
||||
|
||||
export default metadata;
|
||||
|
||||
export const Running: StoryObj = {
|
||||
args: {
|
||||
status: {
|
||||
isRunning: true,
|
||||
tasks: [],
|
||||
} as SyncStatus,
|
||||
},
|
||||
// @ts-ignore
|
||||
render: ({ status }: SyncStatus) => {
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<ak-sync-status-card
|
||||
.fetch=${async () => {
|
||||
return status;
|
||||
}}
|
||||
></ak-sync-status-card>
|
||||
</div>`;
|
||||
},
|
||||
};
|
||||
|
||||
export const SingleTask: StoryObj = {
|
||||
args: {
|
||||
status: {
|
||||
isRunning: false,
|
||||
tasks: [
|
||||
{
|
||||
uuid: "9ff42169-8249-4b67-ae3d-e455d822de2b",
|
||||
name: "Single task",
|
||||
fullName: "foo:bar:baz",
|
||||
status: SystemTaskStatusEnum.Successful,
|
||||
messages: [
|
||||
{
|
||||
logger: "foo",
|
||||
event: "bar",
|
||||
attributes: {
|
||||
foo: "bar",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
logLevel: LogLevelEnum.Info,
|
||||
},
|
||||
],
|
||||
description: "foo",
|
||||
startTimestamp: new Date(),
|
||||
finishTimestamp: new Date(),
|
||||
duration: 0,
|
||||
},
|
||||
],
|
||||
} as SyncStatus,
|
||||
},
|
||||
// @ts-ignore
|
||||
render: ({ status }: SyncStatus) => {
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<ak-sync-status-card
|
||||
.fetch=${async () => {
|
||||
return status;
|
||||
}}
|
||||
></ak-sync-status-card>
|
||||
</div>`;
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleTasks: StoryObj = {
|
||||
args: {
|
||||
status: {
|
||||
isRunning: false,
|
||||
tasks: [
|
||||
{
|
||||
uuid: "9ff42169-8249-4b67-ae3d-e455d822de2b",
|
||||
name: "Single task",
|
||||
fullName: "foo:bar:baz",
|
||||
status: SystemTaskStatusEnum.Successful,
|
||||
messages: [
|
||||
{
|
||||
logger: "foo",
|
||||
event: "bar",
|
||||
attributes: {
|
||||
foo: "bar",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
logLevel: LogLevelEnum.Info,
|
||||
},
|
||||
],
|
||||
description: "foo",
|
||||
startTimestamp: new Date(),
|
||||
finishTimestamp: new Date(),
|
||||
duration: 0,
|
||||
},
|
||||
{
|
||||
uuid: "9ff42169-8249-4b67-ae3d-e455d822de2b",
|
||||
name: "Single task",
|
||||
fullName: "foo:bar:baz",
|
||||
status: SystemTaskStatusEnum.Successful,
|
||||
messages: [
|
||||
{
|
||||
logger: "foo",
|
||||
event: "bar",
|
||||
attributes: {
|
||||
foo: "bar",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
logLevel: LogLevelEnum.Info,
|
||||
},
|
||||
],
|
||||
description: "foo",
|
||||
startTimestamp: new Date(),
|
||||
finishTimestamp: new Date(),
|
||||
duration: 0,
|
||||
},
|
||||
{
|
||||
uuid: "9ff42169-8249-4b67-ae3d-e455d822de2b",
|
||||
name: "Single task",
|
||||
fullName: "foo:bar:baz",
|
||||
status: SystemTaskStatusEnum.Successful,
|
||||
messages: [
|
||||
{
|
||||
logger: "foo",
|
||||
event: "bar",
|
||||
attributes: {
|
||||
foo: "bar",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
logLevel: LogLevelEnum.Info,
|
||||
},
|
||||
],
|
||||
description: "foo",
|
||||
startTimestamp: new Date(),
|
||||
finishTimestamp: new Date(),
|
||||
duration: 0,
|
||||
},
|
||||
],
|
||||
} as SyncStatus,
|
||||
},
|
||||
// @ts-ignore
|
||||
render: ({ status }: SyncStatus) => {
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<ak-sync-status-card
|
||||
.fetch=${async () => {
|
||||
return status;
|
||||
}}
|
||||
></ak-sync-status-card>
|
||||
</div>`;
|
||||
},
|
||||
};
|
@ -5,15 +5,16 @@ import {
|
||||
TITLE_DEFAULT,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { purify } from "@goauthentik/common/purify";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
import "@goauthentik/elements/LoadingOverlay";
|
||||
import "@goauthentik/elements/ak-locale-context";
|
||||
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
|
||||
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
import "@goauthentik/flow/components/ak-brand-footer";
|
||||
import "@goauthentik/flow/sources/apple/AppleLoginInit";
|
||||
import "@goauthentik/flow/sources/plex/PlexLoginInit";
|
||||
import "@goauthentik/flow/stages/FlowErrorStage";
|
||||
@ -44,6 +45,7 @@ import {
|
||||
FlowErrorChallenge,
|
||||
FlowLayoutEnum,
|
||||
FlowsApi,
|
||||
Message,
|
||||
ResponseError,
|
||||
ShellChallenge,
|
||||
UiThemeEnum,
|
||||
@ -83,8 +85,6 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
@state()
|
||||
flowInfo?: ContextualFlowInfo;
|
||||
|
||||
ws: WebsocketClient;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css`
|
||||
:host {
|
||||
@ -174,7 +174,6 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
const inspector = new URL(window.location.toString()).searchParams.get("inspector");
|
||||
if (inspector === "" || inspector === "open") {
|
||||
this.inspectorOpen = true;
|
||||
@ -233,6 +232,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
if (this.challenge.flowInfo) {
|
||||
this.flowInfo = this.challenge.flowInfo;
|
||||
}
|
||||
this.showMessages(this.challenge.messages);
|
||||
return !this.challenge.responseErrors;
|
||||
} catch (exc: unknown) {
|
||||
this.errorMessage(exc as Error | ResponseError | FetchError);
|
||||
@ -265,6 +265,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
if (this.challenge.flowInfo) {
|
||||
this.flowInfo = this.challenge.flowInfo;
|
||||
}
|
||||
this.showMessages(this.challenge.messages);
|
||||
} catch (exc: unknown) {
|
||||
// Catch JSON or Update errors
|
||||
this.errorMessage(exc as Error | ResponseError | FetchError);
|
||||
@ -273,6 +274,15 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
}
|
||||
|
||||
showMessages(messages: Array<Message> | undefined) {
|
||||
for (const message of (messages ??= [])) {
|
||||
showMessage({
|
||||
level: message.level as MessageLevel,
|
||||
message: message.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async errorMessage(error: Error | ResponseError | FetchError): Promise<void> {
|
||||
let body = "";
|
||||
if (error instanceof FetchError) {
|
||||
@ -537,27 +547,10 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
</div>
|
||||
${until(this.renderChallenge())}
|
||||
</div>
|
||||
<footer class="pf-c-login__footer">
|
||||
<ul class="pf-c-list pf-m-inline">
|
||||
${this.brand?.uiFooterLinks?.map((link) => {
|
||||
if (link.href) {
|
||||
return html`${purify(
|
||||
html`<li>
|
||||
<a href="${link.href}"
|
||||
>${link.name}</a
|
||||
>
|
||||
</li>`,
|
||||
)}`;
|
||||
}
|
||||
return html`<li>
|
||||
<span>${link.name}</span>
|
||||
</li>`;
|
||||
})}
|
||||
<li>
|
||||
<span>${msg("Powered by authentik")}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
<ak-brand-links
|
||||
class="pf-c-login__footer"
|
||||
.links=${this.brand?.uiFooterLinks ?? []}
|
||||
></ak-brand-links>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "@goauthentik/elements/messages/MessageContainer";
|
||||
import "@goauthentik/flow/FlowExecutor";
|
||||
// Statically import some stages to speed up load speed
|
||||
import "@goauthentik/flow/stages/access_denied/AccessDeniedStage";
|
||||
@ -13,3 +12,9 @@ import "@goauthentik/flow/stages/identification/IdentificationStage";
|
||||
import "@goauthentik/flow/stages/password/PasswordStage";
|
||||
|
||||
// end of stage import
|
||||
|
||||
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
|
||||
const { ESBuildObserver } = await import("@goauthentik/common/client");
|
||||
|
||||
new ESBuildObserver(process.env.WATCHER_URL);
|
||||
}
|
||||
|
51
web/src/flow/components/ak-brand-footer.ts
Normal file
51
web/src/flow/components/ak-brand-footer.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { purify } from "@goauthentik/common/purify";
|
||||
import { AKElement } from "@goauthentik/elements/Base.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { FooterLink } from "@goauthentik/api";
|
||||
|
||||
const styles = css`
|
||||
.pf-c-list a {
|
||||
color: unset;
|
||||
}
|
||||
ul.pf-c-list.pf-m-inline {
|
||||
justify-content: center;
|
||||
padding: calc(var(--pf-global--spacer--xs) / 2) 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
const poweredBy: FooterLink = { name: msg("Powered by authentik"), href: null };
|
||||
|
||||
@customElement("ak-brand-links")
|
||||
export class BrandLinks extends AKElement {
|
||||
static get styles() {
|
||||
return [PFBase, PFList, styles];
|
||||
}
|
||||
|
||||
@property({ type: Array, attribute: false })
|
||||
links: FooterLink[] = [];
|
||||
|
||||
render() {
|
||||
const links = [...(this.links ?? []), poweredBy];
|
||||
return html` <ul class="pf-c-list pf-m-inline">
|
||||
${map(links, (link) =>
|
||||
link.href
|
||||
? purify(html`<li><a href="${link.href}">${link.name}</a></li>`)
|
||||
: html`<li><span>${link.name}</span></li>`,
|
||||
)}
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-brand-links": BrandLinks;
|
||||
}
|
||||
}
|
@ -70,57 +70,52 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
|
||||
return html`<ak-empty-state loading> </ak-empty-state>`;
|
||||
}
|
||||
return html`<div class="pf-c-login__main-body">
|
||||
<form
|
||||
class="pf-c-form"
|
||||
@submit=${(e: Event) => {
|
||||
this.submitForm(e);
|
||||
}}
|
||||
<form
|
||||
class="pf-c-form"
|
||||
@submit=${(e: Event) => {
|
||||
this.submitForm(e);
|
||||
}}
|
||||
>
|
||||
${this.renderUserInfo()}
|
||||
<div class="icon-description">
|
||||
<i class="fa ${this.deviceIcon()}" aria-hidden="true"></i>
|
||||
<p>${this.deviceMessage()}</p>
|
||||
</div>
|
||||
<ak-form-element
|
||||
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? msg("Static token")
|
||||
: msg("Authentication code")}"
|
||||
required
|
||||
class="pf-c-form__group"
|
||||
.errors=${(this.challenge?.responseErrors || {})["code"]}
|
||||
>
|
||||
${this.renderUserInfo()}
|
||||
<div class="icon-description">
|
||||
<i class="fa ${this.deviceIcon()}" aria-hidden="true"></i>
|
||||
<p>${this.deviceMessage()}</p>
|
||||
</div>
|
||||
<ak-form-element
|
||||
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? msg("Static token")
|
||||
: msg("Authentication code")}"
|
||||
<!-- @ts-ignore -->
|
||||
<input
|
||||
type="text"
|
||||
name="code"
|
||||
inputmode="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? "text"
|
||||
: "numeric"}"
|
||||
pattern="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? "[0-9a-zA-Z]*"
|
||||
: "[0-9]*"}"
|
||||
placeholder="${msg("Please enter your code")}"
|
||||
autofocus=""
|
||||
autocomplete="one-time-code"
|
||||
class="pf-c-form-control"
|
||||
value="${PasswordManagerPrefill.totp || ""}"
|
||||
required
|
||||
class="pf-c-form__group"
|
||||
.errors=${(this.challenge?.responseErrors || {})["code"]}
|
||||
>
|
||||
<!-- @ts-ignore -->
|
||||
<input
|
||||
type="text"
|
||||
name="code"
|
||||
inputmode="${this.deviceChallenge?.deviceClass ===
|
||||
DeviceClassesEnum.Static
|
||||
? "text"
|
||||
: "numeric"}"
|
||||
pattern="${this.deviceChallenge?.deviceClass ===
|
||||
DeviceClassesEnum.Static
|
||||
? "[0-9a-zA-Z]*"
|
||||
: "[0-9]*"}"
|
||||
placeholder="${msg("Please enter your code")}"
|
||||
autofocus=""
|
||||
autocomplete="one-time-code"
|
||||
class="pf-c-form-control"
|
||||
value="${PasswordManagerPrefill.totp || ""}"
|
||||
required
|
||||
/>
|
||||
</ak-form-element>
|
||||
/>
|
||||
</ak-form-element>
|
||||
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||
${msg("Continue")}
|
||||
</button>
|
||||
${this.renderReturnToDevicePicker()}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links"></ul>
|
||||
</footer>`;
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||
${msg("Continue")}
|
||||
</button>
|
||||
${this.renderReturnToDevicePicker()}
|
||||
</div>
|
||||
</form>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,9 +72,7 @@ export class BaseStage<
|
||||
}
|
||||
return this.host?.submit(object as unknown as Tout).then((successful) => {
|
||||
if (successful) {
|
||||
this.onSubmitSuccess();
|
||||
} else {
|
||||
this.onSubmitFailure();
|
||||
this.cleanup();
|
||||
}
|
||||
return successful;
|
||||
});
|
||||
@ -126,11 +124,7 @@ export class BaseStage<
|
||||
`;
|
||||
}
|
||||
|
||||
onSubmitSuccess(): void {
|
||||
// Method that can be overridden by stages
|
||||
return;
|
||||
}
|
||||
onSubmitFailure(): void {
|
||||
cleanup(): void {
|
||||
// Method that can be overridden by stages
|
||||
return;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import { randomId } from "@goauthentik/elements/utils/randomId";
|
||||
import "@goauthentik/flow/FormStatic";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
import { P, match } from "ts-pattern";
|
||||
import type * as _ from "turnstile-types";
|
||||
import type { TurnstileObject } from "turnstile-types";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
|
||||
@ -24,6 +24,10 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { CaptchaChallenge, CaptchaChallengeResponseRequest } from "@goauthentik/api";
|
||||
|
||||
interface TurnstileWindow extends Window {
|
||||
turnstile: TurnstileObject;
|
||||
}
|
||||
|
||||
type TokenHandler = (token: string) => void;
|
||||
|
||||
type Dims = { height: number };
|
||||
@ -48,8 +52,6 @@ type CaptchaHandler = {
|
||||
name: string;
|
||||
interactive: () => Promise<unknown>;
|
||||
execute: () => Promise<unknown>;
|
||||
refreshInteractive: () => Promise<unknown>;
|
||||
refresh: () => Promise<unknown>;
|
||||
};
|
||||
|
||||
// A container iframe for a hosted Captcha, with an event emitter to monitor when the Captcha forces
|
||||
@ -117,12 +119,6 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
this.host.submit({ component: "ak-stage-captcha", token });
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
refreshedAt = new Date();
|
||||
|
||||
@state()
|
||||
activeHandler?: CaptchaHandler = undefined;
|
||||
|
||||
@state()
|
||||
error?: string;
|
||||
|
||||
@ -131,22 +127,16 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
name: "grecaptcha",
|
||||
interactive: this.renderGReCaptchaFrame,
|
||||
execute: this.executeGReCaptcha,
|
||||
refreshInteractive: this.refreshGReCaptchaFrame,
|
||||
refresh: this.refreshGReCaptcha,
|
||||
},
|
||||
{
|
||||
name: "hcaptcha",
|
||||
interactive: this.renderHCaptchaFrame,
|
||||
execute: this.executeHCaptcha,
|
||||
refreshInteractive: this.refreshHCaptchaFrame,
|
||||
refresh: this.refreshHCaptcha,
|
||||
},
|
||||
{
|
||||
name: "turnstile",
|
||||
interactive: this.renderTurnstileFrame,
|
||||
execute: this.executeTurnstile,
|
||||
refreshInteractive: this.refreshTurnstileFrame,
|
||||
refresh: this.refreshTurnstile,
|
||||
},
|
||||
];
|
||||
|
||||
@ -240,15 +230,6 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
});
|
||||
}
|
||||
|
||||
async refreshGReCaptchaFrame() {
|
||||
(this.captchaFrame.contentWindow as typeof window)?.grecaptcha.reset();
|
||||
}
|
||||
|
||||
async refreshGReCaptcha() {
|
||||
window.grecaptcha.reset();
|
||||
window.grecaptcha.execute();
|
||||
}
|
||||
|
||||
async renderHCaptchaFrame() {
|
||||
this.renderFrame(
|
||||
html`<div
|
||||
@ -270,15 +251,6 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
);
|
||||
}
|
||||
|
||||
async refreshHCaptchaFrame() {
|
||||
(this.captchaFrame.contentWindow as typeof window)?.hcaptcha.reset();
|
||||
}
|
||||
|
||||
async refreshHCaptcha() {
|
||||
window.hcaptcha.reset();
|
||||
window.hcaptcha.execute();
|
||||
}
|
||||
|
||||
async renderTurnstileFrame() {
|
||||
this.renderFrame(
|
||||
html`<div
|
||||
@ -290,18 +262,13 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
}
|
||||
|
||||
async executeTurnstile() {
|
||||
return window.turnstile.render(this.captchaDocumentContainer, {
|
||||
sitekey: this.challenge.siteKey,
|
||||
callback: this.onTokenChange,
|
||||
});
|
||||
}
|
||||
|
||||
async refreshTurnstileFrame() {
|
||||
(this.captchaFrame.contentWindow as typeof window)?.turnstile.reset();
|
||||
}
|
||||
|
||||
async refreshTurnstile() {
|
||||
window.turnstile.reset();
|
||||
return (window as unknown as TurnstileWindow).turnstile.render(
|
||||
this.captchaDocumentContainer,
|
||||
{
|
||||
sitekey: this.challenge.siteKey,
|
||||
callback: this.onTokenChange,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async renderFrame(captchaElement: TemplateResult) {
|
||||
@ -369,19 +336,16 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
const handlers = this.handlers.filter(({ name }) => Object.hasOwn(window, name));
|
||||
let lastError = undefined;
|
||||
let found = false;
|
||||
for (const handler of handlers) {
|
||||
console.debug(`authentik/stages/captcha: trying handler ${handler.name}`);
|
||||
for (const { name, interactive, execute } of handlers) {
|
||||
console.debug(`authentik/stages/captcha: trying handler ${name}`);
|
||||
try {
|
||||
const runner = this.challenge.interactive
|
||||
? handler.interactive
|
||||
: handler.execute;
|
||||
const runner = this.challenge.interactive ? interactive : execute;
|
||||
await runner.apply(this);
|
||||
console.debug(`authentik/stages/captcha[${handler.name}]: handler succeeded`);
|
||||
console.debug(`authentik/stages/captcha[${name}]: handler succeeded`);
|
||||
found = true;
|
||||
this.activeHandler = handler;
|
||||
break;
|
||||
} catch (exc) {
|
||||
console.debug(`authentik/stages/captcha[${handler.name}]: handler failed`);
|
||||
console.debug(`authentik/stages/captcha[${name}]: handler failed`);
|
||||
console.debug(exc);
|
||||
lastError = exc;
|
||||
}
|
||||
@ -406,19 +370,6 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
document.body.appendChild(this.captchaDocumentContainer);
|
||||
}
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (!changedProperties.has("refreshedAt") || !this.challenge) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("authentik/stages/captcha: refresh triggered");
|
||||
if (this.challenge.interactive) {
|
||||
this.activeHandler?.refreshInteractive.apply(this);
|
||||
} else {
|
||||
this.activeHandler?.refresh.apply(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -49,8 +49,6 @@ export class IdentificationStage extends BaseStage<
|
||||
|
||||
@state()
|
||||
captchaToken = "";
|
||||
@state()
|
||||
captchaRefreshedAt = new Date();
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
@ -181,16 +179,12 @@ export class IdentificationStage extends BaseStage<
|
||||
this.form.appendChild(totp);
|
||||
}
|
||||
|
||||
onSubmitSuccess(): void {
|
||||
cleanup(): void {
|
||||
if (this.form) {
|
||||
this.form.remove();
|
||||
}
|
||||
}
|
||||
|
||||
onSubmitFailure(): void {
|
||||
this.captchaRefreshedAt = new Date();
|
||||
}
|
||||
|
||||
renderSource(source: LoginSource): TemplateResult {
|
||||
const icon = renderSourceIcon(source.name, source.iconUrl);
|
||||
return html`<li class="pf-c-login__main-footer-links-item">
|
||||
@ -293,7 +287,6 @@ export class IdentificationStage extends BaseStage<
|
||||
.onTokenChange=${(token: string) => {
|
||||
this.captchaToken = token;
|
||||
}}
|
||||
.refreshedAt=${this.captchaRefreshedAt}
|
||||
embedded
|
||||
></ak-stage-captcha>
|
||||
`
|
||||
|
6
web/src/global.d.ts
vendored
6
web/src/global.d.ts
vendored
@ -6,6 +6,12 @@ declare module "*.md" {
|
||||
const filename: string;
|
||||
}
|
||||
|
||||
declare module "*.mdx" {
|
||||
const html: string;
|
||||
const metadata: { [key: string]: string };
|
||||
const filename: string;
|
||||
}
|
||||
|
||||
declare namespace Intl {
|
||||
class ListFormat {
|
||||
constructor(locale: string, args: { [key: string]: string });
|
||||
|
@ -165,21 +165,13 @@ class UserInterfacePresentation extends AKElement {
|
||||
}
|
||||
|
||||
return html`<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/admin/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("Admin interface")}
|
||||
</a>
|
||||
<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none-on-md pf-u-display-block"
|
||||
href="${globalAK().api.base}if/admin/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("Admin")}
|
||||
</a>`;
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/admin/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("Admin interface")}
|
||||
</a>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
// The `!` in the field definitions above only re-assure typescript and eslint that the
|
||||
// values *should* be available, not that they *are*. Thus this contract check; it asserts
|
||||
@ -286,11 +278,17 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
|
||||
window.addEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
|
||||
window.addEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
|
||||
|
||||
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
|
||||
const { ESBuildObserver } = await import("@goauthentik/common/client");
|
||||
|
||||
new ESBuildObserver(process.env.WATCHER_URL);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
@ -59,10 +59,6 @@ export class UserSettingsPage extends AKElement {
|
||||
:host([theme="dark"]) .pf-c-page__main-section {
|
||||
--pf-c-page__main-section--BackgroundColor: transparent;
|
||||
}
|
||||
.pf-c-page__main {
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@media screen and (min-width: 1200px) {
|
||||
:host {
|
||||
width: 90rem;
|
||||
|
@ -8600,7 +8600,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source>This option configures the footer links on the flow executor pages. The URL is limited to web and mail addresses. If the name is left blank, the URL will be shown.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s66f572bec2bde9c4">
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||
<source>Strict</source>
|
||||
@ -8926,6 +8926,96 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s47b7ce63a543564c">
|
||||
<source>Fewer details</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s140111d464591e6b">
|
||||
<source>Create a new application and configure a provider for it.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5e0c81c05565bf42">
|
||||
<source>Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s035bfd9c5f97e4d3">
|
||||
<source>Distance settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s207e6f8a8b3515fd">
|
||||
<source>Check historical distance of logins</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8158f4b3e5c869be">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb8b7450c8515894c">
|
||||
<source>Maximum distance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s40cdbaa532bc9899">
|
||||
<source>Maximum distance a login attempt is allowed from in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="seef852b5c0f8a529">
|
||||
<source>Distance tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sce567ced300aeb8a">
|
||||
<source>Tolerance in checking for distances in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9ea9cdabd74f8f97">
|
||||
<source>Historical Login Count</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s27aec4c2de1ae777">
|
||||
<source>Amount of previous login events to check against.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s48611ce6e85874dc">
|
||||
<source>Check impossible travel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cf926e8311f8065">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa963d05af436770b">
|
||||
<source>Impossible travel tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5760cd97ca42a238">
|
||||
<source>Static rule settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8fec035fa1737294">
|
||||
<source>Create with Provider</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sca2487321ec12bd6">
|
||||
<source>Email address the verification email will be sent from.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s24a8fdfc73e8137f">
|
||||
<source>Stage used to configure an email-based authenticator.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sea0da186a814a212">
|
||||
<source>Use global connection settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7754fa56a4439de4">
|
||||
<source>When enabled, global email connection settings will be used and connection settings below will be ignored.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7e2bcca51126ec9c">
|
||||
<source>Subject of the verification email.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc12c90b1da0f3a47">
|
||||
<source>Token expiration</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc264a82f9c710f14">
|
||||
<source>Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s15986693bfc99fb7">
|
||||
<source>Email-based Authenticators</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6bb30c61df4cf486">
|
||||
<source>Caps Lock is enabled.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3f8a07912545e72e">
|
||||
<source>Configure your email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scedf77e8b75cad5a">
|
||||
<source>Please enter your email address.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7cdd62c100b6b17b">
|
||||
<source>Please enter the code you received via email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d64dba9bb8b284d">
|
||||
<source>A code has been sent to you via email<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s833cfe815918c143">
|
||||
<source>Tokens sent via email.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -7128,7 +7128,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source>This option configures the footer links on the flow executor pages. The URL is limited to web and mail addresses. If the name is left blank, the URL will be shown.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s66f572bec2bde9c4">
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||
<source>Strict</source>
|
||||
@ -7453,6 +7453,96 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s47b7ce63a543564c">
|
||||
<source>Fewer details</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s140111d464591e6b">
|
||||
<source>Create a new application and configure a provider for it.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5e0c81c05565bf42">
|
||||
<source>Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s035bfd9c5f97e4d3">
|
||||
<source>Distance settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s207e6f8a8b3515fd">
|
||||
<source>Check historical distance of logins</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8158f4b3e5c869be">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb8b7450c8515894c">
|
||||
<source>Maximum distance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s40cdbaa532bc9899">
|
||||
<source>Maximum distance a login attempt is allowed from in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="seef852b5c0f8a529">
|
||||
<source>Distance tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sce567ced300aeb8a">
|
||||
<source>Tolerance in checking for distances in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9ea9cdabd74f8f97">
|
||||
<source>Historical Login Count</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s27aec4c2de1ae777">
|
||||
<source>Amount of previous login events to check against.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s48611ce6e85874dc">
|
||||
<source>Check impossible travel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cf926e8311f8065">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa963d05af436770b">
|
||||
<source>Impossible travel tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5760cd97ca42a238">
|
||||
<source>Static rule settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8fec035fa1737294">
|
||||
<source>Create with Provider</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sca2487321ec12bd6">
|
||||
<source>Email address the verification email will be sent from.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s24a8fdfc73e8137f">
|
||||
<source>Stage used to configure an email-based authenticator.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sea0da186a814a212">
|
||||
<source>Use global connection settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7754fa56a4439de4">
|
||||
<source>When enabled, global email connection settings will be used and connection settings below will be ignored.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7e2bcca51126ec9c">
|
||||
<source>Subject of the verification email.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc12c90b1da0f3a47">
|
||||
<source>Token expiration</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc264a82f9c710f14">
|
||||
<source>Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s15986693bfc99fb7">
|
||||
<source>Email-based Authenticators</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6bb30c61df4cf486">
|
||||
<source>Caps Lock is enabled.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3f8a07912545e72e">
|
||||
<source>Configure your email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scedf77e8b75cad5a">
|
||||
<source>Please enter your email address.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7cdd62c100b6b17b">
|
||||
<source>Please enter the code you received via email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d64dba9bb8b284d">
|
||||
<source>A code has been sent to you via email<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s833cfe815918c143">
|
||||
<source>Tokens sent via email.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -8688,7 +8688,7 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
||||
<source>This option configures the footer links on the flow executor pages. The URL is limited to web and mail addresses. If the name is left blank, the URL will be shown.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s66f572bec2bde9c4">
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<target>Aplicaciones externas que utilizan <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> como proveedor de identidad a través de protocolos como OAuth2 y SAML. Aquí se muestran todas las aplicaciones, incluso aquellas a las que no puede acceder.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||
@ -9019,6 +9019,96 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
||||
</trans-unit>
|
||||
<trans-unit id="s47b7ce63a543564c">
|
||||
<source>Fewer details</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s140111d464591e6b">
|
||||
<source>Create a new application and configure a provider for it.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5e0c81c05565bf42">
|
||||
<source>Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s035bfd9c5f97e4d3">
|
||||
<source>Distance settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s207e6f8a8b3515fd">
|
||||
<source>Check historical distance of logins</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8158f4b3e5c869be">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb8b7450c8515894c">
|
||||
<source>Maximum distance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s40cdbaa532bc9899">
|
||||
<source>Maximum distance a login attempt is allowed from in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="seef852b5c0f8a529">
|
||||
<source>Distance tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sce567ced300aeb8a">
|
||||
<source>Tolerance in checking for distances in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9ea9cdabd74f8f97">
|
||||
<source>Historical Login Count</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s27aec4c2de1ae777">
|
||||
<source>Amount of previous login events to check against.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s48611ce6e85874dc">
|
||||
<source>Check impossible travel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cf926e8311f8065">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa963d05af436770b">
|
||||
<source>Impossible travel tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5760cd97ca42a238">
|
||||
<source>Static rule settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8fec035fa1737294">
|
||||
<source>Create with Provider</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sca2487321ec12bd6">
|
||||
<source>Email address the verification email will be sent from.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s24a8fdfc73e8137f">
|
||||
<source>Stage used to configure an email-based authenticator.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sea0da186a814a212">
|
||||
<source>Use global connection settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7754fa56a4439de4">
|
||||
<source>When enabled, global email connection settings will be used and connection settings below will be ignored.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7e2bcca51126ec9c">
|
||||
<source>Subject of the verification email.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc12c90b1da0f3a47">
|
||||
<source>Token expiration</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc264a82f9c710f14">
|
||||
<source>Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s15986693bfc99fb7">
|
||||
<source>Email-based Authenticators</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6bb30c61df4cf486">
|
||||
<source>Caps Lock is enabled.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3f8a07912545e72e">
|
||||
<source>Configure your email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scedf77e8b75cad5a">
|
||||
<source>Please enter your email address.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7cdd62c100b6b17b">
|
||||
<source>Please enter the code you received via email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d64dba9bb8b284d">
|
||||
<source>A code has been sent to you via email<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s833cfe815918c143">
|
||||
<source>Tokens sent via email.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
126
web/xliff/fr.xlf
126
web/xliff/fr.xlf
@ -6572,7 +6572,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
||||
</trans-unit>
|
||||
<trans-unit id="s0e516232f2ab4e04">
|
||||
<source>Tokens sent via SMS.</source>
|
||||
<target>Jeton envoyé par SMS</target>
|
||||
<target>Jetons envoyés par SMS.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s6ae0d087036e6d6d">
|
||||
@ -9046,8 +9046,8 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
||||
<target>Cette option configure les liens affichés en bas de page sur l’exécuteur de flux. L'URL est limitée à des addresses web et courriel. Si le nom est laissé vide, l'URL sera affichée.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s66f572bec2bde9c4">
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<target>Applications externes qui utilisent <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> comme fournisseur d'identité en utilisant des protocoles comme OAuth2 et SAML. Toutes les applications sont affichées ici, même celles auxquelles vous n'avez pas accès.</target>
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<target>Applications externes qui utilisent <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> comme fournisseur d'identité en utilisant des protocoles comme OAuth2 et SAML. Toutes les applications sont affichées ici, même celles auxquelles vous n'avez pas accès.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||
<source>Strict</source>
|
||||
@ -9482,6 +9482,126 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
||||
<trans-unit id="s47b7ce63a543564c">
|
||||
<source>Fewer details</source>
|
||||
<target>Moins de détails</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s140111d464591e6b">
|
||||
<source>Create a new application and configure a provider for it.</source>
|
||||
<target>Créer une nouvelle application et configurer un fournisseur pour celle-ci.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5e0c81c05565bf42">
|
||||
<source>Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.</source>
|
||||
<target>L'utilisation de ce formulaire ne créera qu'une application. Afin de vous authentifier auprès de l'application, vous devrez l'associer manuellement à un fournisseur.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s035bfd9c5f97e4d3">
|
||||
<source>Distance settings</source>
|
||||
<target>Réglages de distance</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s207e6f8a8b3515fd">
|
||||
<source>Check historical distance of logins</source>
|
||||
<target>Vérifier l'historique de distance des connexions</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8158f4b3e5c869be">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.</source>
|
||||
<target>Lorsque cette option est activée, les données GeoIP de la demande de politique sont comparées au nombre spécifié de connexions historiques.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb8b7450c8515894c">
|
||||
<source>Maximum distance</source>
|
||||
<target>Distance maximale</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s40cdbaa532bc9899">
|
||||
<source>Maximum distance a login attempt is allowed from in kilometers.</source>
|
||||
<target>Distance maximale autorisée pour une tentative de connexion en kilomètres.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="seef852b5c0f8a529">
|
||||
<source>Distance tolerance</source>
|
||||
<target>Tolérance de distance</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sce567ced300aeb8a">
|
||||
<source>Tolerance in checking for distances in kilometers.</source>
|
||||
<target>Tolérance de vérification des distances en kilomètres.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9ea9cdabd74f8f97">
|
||||
<source>Historical Login Count</source>
|
||||
<target>Nombre de connexions historiques</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s27aec4c2de1ae777">
|
||||
<source>Amount of previous login events to check against.</source>
|
||||
<target>Nombre d'événements de connexion précédents à vérifier.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s48611ce6e85874dc">
|
||||
<source>Check impossible travel</source>
|
||||
<target>Vérifier les déplacements impossibles</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cf926e8311f8065">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.</source>
|
||||
<target>Lorsque cette option est activée, les données GeoIP de la demande de politique sont comparées au nombre spécifié de connexions historiques et si le voyage aurait été possible dans le laps de temps écoulé depuis l'événement précédent.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa963d05af436770b">
|
||||
<source>Impossible travel tolerance</source>
|
||||
<target>Tolérance de déplacement impossible</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5760cd97ca42a238">
|
||||
<source>Static rule settings</source>
|
||||
<target>Paramètres de règle statique</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8fec035fa1737294">
|
||||
<source>Create with Provider</source>
|
||||
<target>Créer avec un fournisseur</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sca2487321ec12bd6">
|
||||
<source>Email address the verification email will be sent from.</source>
|
||||
<target>Adresse courriel depuis laquelle le courriel de vérification sera envoyé.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s24a8fdfc73e8137f">
|
||||
<source>Stage used to configure an email-based authenticator.</source>
|
||||
<target>Étape utilisée pour configurer un authentificateur courriel.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sea0da186a814a212">
|
||||
<source>Use global connection settings</source>
|
||||
<target>Utiliser les paramètres de connexion globaux</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7754fa56a4439de4">
|
||||
<source>When enabled, global email connection settings will be used and connection settings below will be ignored.</source>
|
||||
<target>Si activé, les paramètres globaux de connexion courriel seront utilisés et les paramètres de connexion ci-dessous seront ignorés.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7e2bcca51126ec9c">
|
||||
<source>Subject of the verification email.</source>
|
||||
<target>Objet du courriel de vérification.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc12c90b1da0f3a47">
|
||||
<source>Token expiration</source>
|
||||
<target>Expiration du jeton</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc264a82f9c710f14">
|
||||
<source>Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).</source>
|
||||
<target>Durée de validité du jeton envoyé (Format : hours=3,minutes=17,seconds=300).</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s15986693bfc99fb7">
|
||||
<source>Email-based Authenticators</source>
|
||||
<target>Authenticatificateurs basé sur courriel</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6bb30c61df4cf486">
|
||||
<source>Caps Lock is enabled.</source>
|
||||
<target>La touche Verr Maj est activée.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3f8a07912545e72e">
|
||||
<source>Configure your email</source>
|
||||
<target>Configurer votre courriel</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="scedf77e8b75cad5a">
|
||||
<source>Please enter your email address.</source>
|
||||
<target>Veuillez entrer votre adresse courriel.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7cdd62c100b6b17b">
|
||||
<source>Please enter the code you received via email</source>
|
||||
<target>Veuillez entrer le code que vous avez reçu par courriel</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d64dba9bb8b284d">
|
||||
<source>A code has been sent to you via email<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></source>
|
||||
<target>Un code vous a été envoyé par courriel<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s833cfe815918c143">
|
||||
<source>Tokens sent via email.</source>
|
||||
<target>Jetons envoyés par courriel.</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -9015,7 +9015,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>Questo opzione configura il link in basso nel flusso delle pagine di esecuzione. L'URL e' limitato a web e indirizzo mail-Se il nome viene lasciato vuoto, verra' visualizzato l'URL</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s66f572bec2bde9c4">
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<target>Applicazioni esterne che usano <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> come identity provider tramite protocolli come OAuth2 e SAML. Sono mostrate tutte le applicazioni, anche quelle alle quali non hai accesso.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||
@ -9370,6 +9370,96 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s47b7ce63a543564c">
|
||||
<source>Fewer details</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s140111d464591e6b">
|
||||
<source>Create a new application and configure a provider for it.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5e0c81c05565bf42">
|
||||
<source>Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s035bfd9c5f97e4d3">
|
||||
<source>Distance settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s207e6f8a8b3515fd">
|
||||
<source>Check historical distance of logins</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8158f4b3e5c869be">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb8b7450c8515894c">
|
||||
<source>Maximum distance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s40cdbaa532bc9899">
|
||||
<source>Maximum distance a login attempt is allowed from in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="seef852b5c0f8a529">
|
||||
<source>Distance tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sce567ced300aeb8a">
|
||||
<source>Tolerance in checking for distances in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9ea9cdabd74f8f97">
|
||||
<source>Historical Login Count</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s27aec4c2de1ae777">
|
||||
<source>Amount of previous login events to check against.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s48611ce6e85874dc">
|
||||
<source>Check impossible travel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cf926e8311f8065">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa963d05af436770b">
|
||||
<source>Impossible travel tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5760cd97ca42a238">
|
||||
<source>Static rule settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8fec035fa1737294">
|
||||
<source>Create with Provider</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sca2487321ec12bd6">
|
||||
<source>Email address the verification email will be sent from.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s24a8fdfc73e8137f">
|
||||
<source>Stage used to configure an email-based authenticator.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sea0da186a814a212">
|
||||
<source>Use global connection settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7754fa56a4439de4">
|
||||
<source>When enabled, global email connection settings will be used and connection settings below will be ignored.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7e2bcca51126ec9c">
|
||||
<source>Subject of the verification email.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc12c90b1da0f3a47">
|
||||
<source>Token expiration</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc264a82f9c710f14">
|
||||
<source>Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s15986693bfc99fb7">
|
||||
<source>Email-based Authenticators</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6bb30c61df4cf486">
|
||||
<source>Caps Lock is enabled.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3f8a07912545e72e">
|
||||
<source>Configure your email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scedf77e8b75cad5a">
|
||||
<source>Please enter your email address.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7cdd62c100b6b17b">
|
||||
<source>Please enter the code you received via email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d64dba9bb8b284d">
|
||||
<source>A code has been sent to you via email<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s833cfe815918c143">
|
||||
<source>Tokens sent via email.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -8600,7 +8600,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source>This option configures the footer links on the flow executor pages. The URL is limited to web and mail addresses. If the name is left blank, the URL will be shown.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s66f572bec2bde9c4">
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||
<source>Strict</source>
|
||||
@ -8926,6 +8926,96 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s47b7ce63a543564c">
|
||||
<source>Fewer details</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s140111d464591e6b">
|
||||
<source>Create a new application and configure a provider for it.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5e0c81c05565bf42">
|
||||
<source>Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s035bfd9c5f97e4d3">
|
||||
<source>Distance settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s207e6f8a8b3515fd">
|
||||
<source>Check historical distance of logins</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8158f4b3e5c869be">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb8b7450c8515894c">
|
||||
<source>Maximum distance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s40cdbaa532bc9899">
|
||||
<source>Maximum distance a login attempt is allowed from in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="seef852b5c0f8a529">
|
||||
<source>Distance tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sce567ced300aeb8a">
|
||||
<source>Tolerance in checking for distances in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9ea9cdabd74f8f97">
|
||||
<source>Historical Login Count</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s27aec4c2de1ae777">
|
||||
<source>Amount of previous login events to check against.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s48611ce6e85874dc">
|
||||
<source>Check impossible travel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cf926e8311f8065">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa963d05af436770b">
|
||||
<source>Impossible travel tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5760cd97ca42a238">
|
||||
<source>Static rule settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8fec035fa1737294">
|
||||
<source>Create with Provider</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sca2487321ec12bd6">
|
||||
<source>Email address the verification email will be sent from.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s24a8fdfc73e8137f">
|
||||
<source>Stage used to configure an email-based authenticator.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sea0da186a814a212">
|
||||
<source>Use global connection settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7754fa56a4439de4">
|
||||
<source>When enabled, global email connection settings will be used and connection settings below will be ignored.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7e2bcca51126ec9c">
|
||||
<source>Subject of the verification email.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc12c90b1da0f3a47">
|
||||
<source>Token expiration</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc264a82f9c710f14">
|
||||
<source>Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s15986693bfc99fb7">
|
||||
<source>Email-based Authenticators</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6bb30c61df4cf486">
|
||||
<source>Caps Lock is enabled.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3f8a07912545e72e">
|
||||
<source>Configure your email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scedf77e8b75cad5a">
|
||||
<source>Please enter your email address.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7cdd62c100b6b17b">
|
||||
<source>Please enter the code you received via email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d64dba9bb8b284d">
|
||||
<source>A code has been sent to you via email<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s833cfe815918c143">
|
||||
<source>Tokens sent via email.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -8501,7 +8501,7 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
||||
<source>This option configures the footer links on the flow executor pages. The URL is limited to web and mail addresses. If the name is left blank, the URL will be shown.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s66f572bec2bde9c4">
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand.brandingTitle || "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
<source>External applications that use <x id="0" equiv-text="${this.brand?.brandingTitle ?? "authentik"}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||
<source>Strict</source>
|
||||
@ -8827,6 +8827,96 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
||||
</trans-unit>
|
||||
<trans-unit id="s47b7ce63a543564c">
|
||||
<source>Fewer details</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s140111d464591e6b">
|
||||
<source>Create a new application and configure a provider for it.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5e0c81c05565bf42">
|
||||
<source>Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s035bfd9c5f97e4d3">
|
||||
<source>Distance settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s207e6f8a8b3515fd">
|
||||
<source>Check historical distance of logins</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8158f4b3e5c869be">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb8b7450c8515894c">
|
||||
<source>Maximum distance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s40cdbaa532bc9899">
|
||||
<source>Maximum distance a login attempt is allowed from in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="seef852b5c0f8a529">
|
||||
<source>Distance tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sce567ced300aeb8a">
|
||||
<source>Tolerance in checking for distances in kilometers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9ea9cdabd74f8f97">
|
||||
<source>Historical Login Count</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s27aec4c2de1ae777">
|
||||
<source>Amount of previous login events to check against.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s48611ce6e85874dc">
|
||||
<source>Check impossible travel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cf926e8311f8065">
|
||||
<source>When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa963d05af436770b">
|
||||
<source>Impossible travel tolerance</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5760cd97ca42a238">
|
||||
<source>Static rule settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8fec035fa1737294">
|
||||
<source>Create with Provider</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sca2487321ec12bd6">
|
||||
<source>Email address the verification email will be sent from.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s24a8fdfc73e8137f">
|
||||
<source>Stage used to configure an email-based authenticator.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sea0da186a814a212">
|
||||
<source>Use global connection settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7754fa56a4439de4">
|
||||
<source>When enabled, global email connection settings will be used and connection settings below will be ignored.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7e2bcca51126ec9c">
|
||||
<source>Subject of the verification email.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc12c90b1da0f3a47">
|
||||
<source>Token expiration</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc264a82f9c710f14">
|
||||
<source>Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s15986693bfc99fb7">
|
||||
<source>Email-based Authenticators</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6bb30c61df4cf486">
|
||||
<source>Caps Lock is enabled.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3f8a07912545e72e">
|
||||
<source>Configure your email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scedf77e8b75cad5a">
|
||||
<source>Please enter your email address.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7cdd62c100b6b17b">
|
||||
<source>Please enter the code you received via email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d64dba9bb8b284d">
|
||||
<source>A code has been sent to you via email<x id="0" equiv-text="${email ? ` ${email}` : ""}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s833cfe815918c143">
|
||||
<source>Tokens sent via email.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user