Compare commits
	
		
			113 Commits
		
	
	
		
			dependabot
			...
			rfc8414
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c57344ac6e | |||
| 7a8c2e7ad9 | |||
| a57381ca4a | |||
| 154dde9a9a | |||
| a15365a9f1 | |||
| 10f11cbc31 | |||
| caec23d52a | |||
| 7e1781ed76 | |||
| 0cfdbbbec6 | |||
| 8a1b7cb166 | |||
| f367a84676 | |||
| 32d6b03a3c | |||
| 08027bf0ad | |||
| 8c02b25677 | |||
| 160f137707 | |||
| 52c35fab06 | |||
| 69a07c1c88 | |||
| 691a0d66ee | |||
| 3f4328bf2a | |||
| b945552b7c | |||
| 5347b85c9f | |||
| fb2401cf9e | |||
| b161315811 | |||
| 0fa2267b86 | |||
| 4bbdddb876 | |||
| bca9c0965e | |||
| dd58b5044e | |||
| c4f081cb68 | |||
| 59aad31459 | |||
| de9db3cb83 | |||
| 24eb5fcda9 | |||
| 556ae6a5cb | |||
| a479d9c1d8 | |||
| b8bb969ee7 | |||
| 7d361e4734 | |||
| dc7c7686a3 | |||
| 94b4977397 | |||
| 7f822e1cb7 | |||
| fb3ec1f38b | |||
| 87505517ee | |||
| 4c5fe84f92 | |||
| 5faa224c81 | |||
| 736da3abef | |||
| 52d90f8d3b | |||
| 7b812de977 | |||
| a4bd2cc263 | |||
| 14038ba8d2 | |||
| eaff59b6b0 | |||
| cb702ca07a | |||
| cb0bfb0dad | |||
| bf46d5c916 | |||
| 59e686c8b9 | |||
| 9e736f2838 | |||
| c2dd3d9c1b | |||
| 42302d3187 | |||
| 20ccabf3ec | |||
| 8f939fa577 | |||
| 2519bcef89 | |||
| 3e3615a859 | |||
| 79e82c8dc9 | |||
| ccd4432e1f | |||
| b3137f5307 | |||
| 2591ed9840 | |||
| b3e89ef570 | |||
| 45b48c5cd6 | |||
| 1eefd834fc | |||
| 4cc6ed97c5 | |||
| bb55d9b3de | |||
| 3972afb865 | |||
| 04a013cc1b | |||
| fb396f7737 | |||
| cf120ff3ff | |||
| 3e4923d52e | |||
| 01793088f0 | |||
| e2bf2ec2cc | |||
| 4dfbe28709 | |||
| b2021a7191 | |||
| 81e5fb0c18 | |||
| a2a2d940a8 | |||
| c034930219 | |||
| da3dc51d87 | |||
| d217a39513 | |||
| 7729a9317c | |||
| be5f5dd3f0 | |||
| bed8d5da4b | |||
| 4f70f84e80 | |||
| 97b8551866 | |||
| 9a0b67e700 | |||
| 97e4c89cec | |||
| 65aedde8f7 | |||
| 17450f23bf | |||
| ab3ad6b7fd | |||
| 45bc3cbd41 | |||
| 9c1bcac6af | |||
| 0a133265c5 | |||
| 57f25a97c9 | |||
| 8f32242787 | |||
| c4bb19051d | |||
| 10f4fae711 | |||
| 2d9eab3f60 | |||
| fa66195619 | |||
| 134eb126b6 | |||
| f5a6136a58 | |||
| 1a82dfcd61 | |||
| 61fc1dc1fb | |||
| 1f921cc18e | |||
| 2f94ee3f1f | |||
| 154fba12e0 | |||
| 0d18c1d797 | |||
| e905dd52d8 | |||
| 245126a1c3 | |||
| 15d84d30ba | |||
| 917b4ab392 | 
@ -1,16 +1,16 @@
 | 
			
		||||
[bumpversion]
 | 
			
		||||
current_version = 2025.4.1
 | 
			
		||||
current_version = 2025.6.0
 | 
			
		||||
tag = True
 | 
			
		||||
commit = True
 | 
			
		||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
 | 
			
		||||
serialize = 
 | 
			
		||||
serialize =
 | 
			
		||||
	{major}.{minor}.{patch}-{rc_t}{rc_n}
 | 
			
		||||
	{major}.{minor}.{patch}
 | 
			
		||||
message = release: {new_version}
 | 
			
		||||
tag_name = version/{new_version}
 | 
			
		||||
 | 
			
		||||
[bumpversion:part:rc_t]
 | 
			
		||||
values = 
 | 
			
		||||
values =
 | 
			
		||||
	rc
 | 
			
		||||
	final
 | 
			
		||||
optional_value = final
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							@ -36,7 +36,7 @@ runs:
 | 
			
		||||
      with:
 | 
			
		||||
        go-version-file: "go.mod"
 | 
			
		||||
    - name: Setup docker cache
 | 
			
		||||
      uses: ScribeMD/docker-cache@0.5.0
 | 
			
		||||
      uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
 | 
			
		||||
      with:
 | 
			
		||||
        key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
 | 
			
		||||
    - name: Setup dependencies
 | 
			
		||||
 | 
			
		||||
@ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
 | 
			
		||||
    /bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
 | 
			
		||||
 | 
			
		||||
# Stage 5: Download uv
 | 
			
		||||
FROM ghcr.io/astral-sh/uv:0.7.8 AS uv
 | 
			
		||||
FROM ghcr.io/astral-sh/uv:0.7.10 AS uv
 | 
			
		||||
# Stage 6: Base python image
 | 
			
		||||
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,8 +20,8 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
 | 
			
		||||
 | 
			
		||||
| Version   | Supported |
 | 
			
		||||
| --------- | --------- |
 | 
			
		||||
| 2025.2.x  | ✅        |
 | 
			
		||||
| 2025.4.x  | ✅        |
 | 
			
		||||
| 2025.6.x  | ✅        |
 | 
			
		||||
 | 
			
		||||
## Reporting a Vulnerability
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
from os import environ
 | 
			
		||||
 | 
			
		||||
__version__ = "2025.4.1"
 | 
			
		||||
__version__ = "2025.6.0"
 | 
			
		||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -84,6 +84,7 @@ from authentik.flows.views.executor import QS_KEY_TOKEN
 | 
			
		||||
from authentik.lib.avatars import get_avatar
 | 
			
		||||
from authentik.rbac.decorators import permission_required
 | 
			
		||||
from authentik.rbac.models import get_permission_choices
 | 
			
		||||
from authentik.stages.email.flow import pickle_flow_token_for_email
 | 
			
		||||
from authentik.stages.email.models import EmailStage
 | 
			
		||||
from authentik.stages.email.tasks import send_mails
 | 
			
		||||
from authentik.stages.email.utils import TemplateEmailMessage
 | 
			
		||||
@ -451,7 +452,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
 | 
			
		||||
    def list(self, request, *args, **kwargs):
 | 
			
		||||
        return super().list(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def _create_recovery_link(self) -> tuple[str, Token]:
 | 
			
		||||
    def _create_recovery_link(self, for_email=False) -> tuple[str, Token]:
 | 
			
		||||
        """Create a recovery link (when the current brand has a recovery flow set),
 | 
			
		||||
        that can either be shown to an admin or sent to the user directly"""
 | 
			
		||||
        brand: Brand = self.request._request.brand
 | 
			
		||||
@ -473,12 +474,16 @@ class UserViewSet(UsedByMixin, ModelViewSet):
 | 
			
		||||
            raise ValidationError(
 | 
			
		||||
                {"non_field_errors": "Recovery flow not applicable to user"}
 | 
			
		||||
            ) from None
 | 
			
		||||
        _plan = FlowToken.pickle(plan)
 | 
			
		||||
        if for_email:
 | 
			
		||||
            _plan = pickle_flow_token_for_email(plan)
 | 
			
		||||
        token, __ = FlowToken.objects.update_or_create(
 | 
			
		||||
            identifier=f"{user.uid}-password-reset",
 | 
			
		||||
            defaults={
 | 
			
		||||
                "user": user,
 | 
			
		||||
                "flow": flow,
 | 
			
		||||
                "_plan": FlowToken.pickle(plan),
 | 
			
		||||
                "_plan": _plan,
 | 
			
		||||
                "revoke_on_execution": not for_email,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        querystring = urlencode({QS_KEY_TOKEN: token.key})
 | 
			
		||||
@ -648,7 +653,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
 | 
			
		||||
        if for_user.email == "":
 | 
			
		||||
            LOGGER.debug("User doesn't have an email address")
 | 
			
		||||
            raise ValidationError({"non_field_errors": "User does not have an email address set."})
 | 
			
		||||
        link, token = self._create_recovery_link()
 | 
			
		||||
        link, token = self._create_recovery_link(for_email=True)
 | 
			
		||||
        # Lookup the email stage to assure the current user can access it
 | 
			
		||||
        stages = get_objects_for_user(
 | 
			
		||||
            request.user, "authentik_stages_email.view_emailstage"
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,7 @@ def _migrate_session(
 | 
			
		||||
        AuthenticatedSession.objects.using(db_alias).create(
 | 
			
		||||
            session=session,
 | 
			
		||||
            user=old_auth_session.user,
 | 
			
		||||
            uuid=old_auth_session.uuid,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,81 @@
 | 
			
		||||
# Generated by Django 5.1.9 on 2025-05-14 11:15
 | 
			
		||||
 | 
			
		||||
from django.apps.registry import Apps
 | 
			
		||||
from django.apps.registry import Apps, apps as global_apps
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
from django.contrib.contenttypes.management import create_contenttypes
 | 
			
		||||
from django.contrib.auth.management import create_permissions
 | 
			
		||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def migrate_authenticated_session_permissions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
 | 
			
		||||
    """Migrate permissions from OldAuthenticatedSession to AuthenticatedSession"""
 | 
			
		||||
    db_alias = schema_editor.connection.alias
 | 
			
		||||
 | 
			
		||||
    # `apps` here is just an instance of `django.db.migrations.state.AppConfigStub`, we need the
 | 
			
		||||
    # real config for creating permissions and content types
 | 
			
		||||
    authentik_core_config = global_apps.get_app_config("authentik_core")
 | 
			
		||||
    # These are only ran by django after all migrations, but we need them right now.
 | 
			
		||||
    # `global_apps` is needed,
 | 
			
		||||
    create_permissions(authentik_core_config, using=db_alias, verbosity=1)
 | 
			
		||||
    create_contenttypes(authentik_core_config, using=db_alias, verbosity=1)
 | 
			
		||||
 | 
			
		||||
    # But from now on, this is just a regular migration, so use `apps`
 | 
			
		||||
    Permission = apps.get_model("auth", "Permission")
 | 
			
		||||
    ContentType = apps.get_model("contenttypes", "ContentType")
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        old_ct = ContentType.objects.using(db_alias).get(
 | 
			
		||||
            app_label="authentik_core", model="oldauthenticatedsession"
 | 
			
		||||
        )
 | 
			
		||||
        new_ct = ContentType.objects.using(db_alias).get(
 | 
			
		||||
            app_label="authentik_core", model="authenticatedsession"
 | 
			
		||||
        )
 | 
			
		||||
    except ContentType.DoesNotExist:
 | 
			
		||||
        # This should exist at this point, but if not, let's cut our losses
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    # Get all permissions for the old content type
 | 
			
		||||
    old_perms = Permission.objects.using(db_alias).filter(content_type=old_ct)
 | 
			
		||||
 | 
			
		||||
    # Create equivalent permissions for the new content type
 | 
			
		||||
    for old_perm in old_perms:
 | 
			
		||||
        new_perm = (
 | 
			
		||||
            Permission.objects.using(db_alias)
 | 
			
		||||
            .filter(
 | 
			
		||||
                content_type=new_ct,
 | 
			
		||||
                codename=old_perm.codename,
 | 
			
		||||
            )
 | 
			
		||||
            .first()
 | 
			
		||||
        )
 | 
			
		||||
        if not new_perm:
 | 
			
		||||
            # This should exist at this point, but if not, let's cut our losses
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # Global user permissions
 | 
			
		||||
        User = apps.get_model("authentik_core", "User")
 | 
			
		||||
        User.user_permissions.through.objects.using(db_alias).filter(
 | 
			
		||||
            permission=old_perm
 | 
			
		||||
        ).all().update(permission=new_perm)
 | 
			
		||||
 | 
			
		||||
        # Global role permissions
 | 
			
		||||
        DjangoGroup = apps.get_model("auth", "Group")
 | 
			
		||||
        DjangoGroup.permissions.through.objects.using(db_alias).filter(
 | 
			
		||||
            permission=old_perm
 | 
			
		||||
        ).all().update(permission=new_perm)
 | 
			
		||||
 | 
			
		||||
        # Object user permissions
 | 
			
		||||
        UserObjectPermission = apps.get_model("guardian", "UserObjectPermission")
 | 
			
		||||
        UserObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
 | 
			
		||||
            permission=new_perm, content_type=new_ct
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Object role permissions
 | 
			
		||||
        GroupObjectPermission = apps.get_model("guardian", "GroupObjectPermission")
 | 
			
		||||
        GroupObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
 | 
			
		||||
            permission=new_perm, content_type=new_ct
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def remove_old_authenticated_session_content_type(
 | 
			
		||||
    apps: Apps, schema_editor: BaseDatabaseSchemaEditor
 | 
			
		||||
):
 | 
			
		||||
@ -21,7 +92,12 @@ class Migration(migrations.Migration):
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RunPython(
 | 
			
		||||
            code=migrate_authenticated_session_permissions,
 | 
			
		||||
            reverse_code=migrations.RunPython.noop,
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RunPython(
 | 
			
		||||
            code=remove_old_authenticated_session_content_type,
 | 
			
		||||
            reverse_code=migrations.RunPython.noop,
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
# Generated by Django 5.1.9 on 2025-05-27 12:52
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("authentik_flows", "0027_auto_20231028_1424"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="flowtoken",
 | 
			
		||||
            name="revoke_on_execution",
 | 
			
		||||
            field=models.BooleanField(default=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -303,9 +303,10 @@ class FlowToken(Token):
 | 
			
		||||
 | 
			
		||||
    flow = models.ForeignKey(Flow, on_delete=models.CASCADE)
 | 
			
		||||
    _plan = models.TextField()
 | 
			
		||||
    revoke_on_execution = models.BooleanField(default=True)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def pickle(plan) -> str:
 | 
			
		||||
    def pickle(plan: "FlowPlan") -> str:
 | 
			
		||||
        """Pickle into string"""
 | 
			
		||||
        data = dumps(plan)
 | 
			
		||||
        return b64encode(data).decode()
 | 
			
		||||
 | 
			
		||||
@ -99,9 +99,10 @@ class ChallengeStageView(StageView):
 | 
			
		||||
            self.logger.debug("Got StageInvalidException", exc=exc)
 | 
			
		||||
            return self.executor.stage_invalid()
 | 
			
		||||
        if not challenge.is_valid():
 | 
			
		||||
            self.logger.warning(
 | 
			
		||||
            self.logger.error(
 | 
			
		||||
                "f(ch): Invalid challenge",
 | 
			
		||||
                errors=challenge.errors,
 | 
			
		||||
                challenge=challenge.data,
 | 
			
		||||
            )
 | 
			
		||||
        return HttpChallengeResponse(challenge)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -146,7 +146,8 @@ class FlowExecutorView(APIView):
 | 
			
		||||
        except (AttributeError, EOFError, ImportError, IndexError) as exc:
 | 
			
		||||
            LOGGER.warning("f(exec): Failed to restore token plan", exc=exc)
 | 
			
		||||
        finally:
 | 
			
		||||
            token.delete()
 | 
			
		||||
            if token.revoke_on_execution:
 | 
			
		||||
                token.delete()
 | 
			
		||||
        if not isinstance(plan, FlowPlan):
 | 
			
		||||
            return None
 | 
			
		||||
        plan.context[PLAN_CONTEXT_IS_RESTORED] = token
 | 
			
		||||
 | 
			
		||||
@ -81,7 +81,6 @@ debugger: false
 | 
			
		||||
 | 
			
		||||
log_level: info
 | 
			
		||||
 | 
			
		||||
session_storage: cache
 | 
			
		||||
sessions:
 | 
			
		||||
  unauthenticated_age: days=1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
from collections.abc import Callable
 | 
			
		||||
from dataclasses import asdict
 | 
			
		||||
 | 
			
		||||
from celery import group
 | 
			
		||||
from celery.exceptions import Retry
 | 
			
		||||
from celery.result import allow_join_result
 | 
			
		||||
from django.core.paginator import Paginator
 | 
			
		||||
@ -82,21 +83,41 @@ class SyncTasks:
 | 
			
		||||
                self.logger.debug("Failed to acquire sync lock, skipping", provider=provider.name)
 | 
			
		||||
                return
 | 
			
		||||
            try:
 | 
			
		||||
                for page in users_paginator.page_range:
 | 
			
		||||
                    messages.append(_("Syncing page {page} of users".format(page=page)))
 | 
			
		||||
                    for msg in sync_objects.apply_async(
 | 
			
		||||
                        args=(class_to_path(User), page, provider_pk),
 | 
			
		||||
                        time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                        soft_time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                    ).get():
 | 
			
		||||
                messages.append(_("Syncing users"))
 | 
			
		||||
                user_results = (
 | 
			
		||||
                    group(
 | 
			
		||||
                        [
 | 
			
		||||
                            sync_objects.signature(
 | 
			
		||||
                                args=(class_to_path(User), page, provider_pk),
 | 
			
		||||
                                time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                                soft_time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                            )
 | 
			
		||||
                            for page in users_paginator.page_range
 | 
			
		||||
                        ]
 | 
			
		||||
                    )
 | 
			
		||||
                    .apply_async()
 | 
			
		||||
                    .get()
 | 
			
		||||
                )
 | 
			
		||||
                for result in user_results:
 | 
			
		||||
                    for msg in result:
 | 
			
		||||
                        messages.append(LogEvent(**msg))
 | 
			
		||||
                for page in groups_paginator.page_range:
 | 
			
		||||
                    messages.append(_("Syncing page {page} of groups".format(page=page)))
 | 
			
		||||
                    for msg in sync_objects.apply_async(
 | 
			
		||||
                        args=(class_to_path(Group), page, provider_pk),
 | 
			
		||||
                        time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                        soft_time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                    ).get():
 | 
			
		||||
                messages.append(_("Syncing groups"))
 | 
			
		||||
                group_results = (
 | 
			
		||||
                    group(
 | 
			
		||||
                        [
 | 
			
		||||
                            sync_objects.signature(
 | 
			
		||||
                                args=(class_to_path(Group), page, provider_pk),
 | 
			
		||||
                                time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                                soft_time_limit=PAGE_TIMEOUT,
 | 
			
		||||
                            )
 | 
			
		||||
                            for page in groups_paginator.page_range
 | 
			
		||||
                        ]
 | 
			
		||||
                    )
 | 
			
		||||
                    .apply_async()
 | 
			
		||||
                    .get()
 | 
			
		||||
                )
 | 
			
		||||
                for result in group_results:
 | 
			
		||||
                    for msg in result:
 | 
			
		||||
                        messages.append(LogEvent(**msg))
 | 
			
		||||
            except TransientSyncException as exc:
 | 
			
		||||
                self.logger.warning("transient sync exception", exc=exc)
 | 
			
		||||
@ -109,7 +130,7 @@ class SyncTasks:
 | 
			
		||||
    def sync_objects(
 | 
			
		||||
        self, object_type: str, page: int, provider_pk: int, override_dry_run=False, **filter
 | 
			
		||||
    ):
 | 
			
		||||
        _object_type = path_to_class(object_type)
 | 
			
		||||
        _object_type: type[Model] = path_to_class(object_type)
 | 
			
		||||
        self.logger = get_logger().bind(
 | 
			
		||||
            provider_type=class_to_path(self._provider_model),
 | 
			
		||||
            provider_pk=provider_pk,
 | 
			
		||||
@ -132,6 +153,19 @@ class SyncTasks:
 | 
			
		||||
            self.logger.debug("starting discover")
 | 
			
		||||
            client.discover()
 | 
			
		||||
        self.logger.debug("starting sync for page", page=page)
 | 
			
		||||
        messages.append(
 | 
			
		||||
            asdict(
 | 
			
		||||
                LogEvent(
 | 
			
		||||
                    _(
 | 
			
		||||
                        "Syncing page {page} of {object_type}".format(
 | 
			
		||||
                            page=page, object_type=_object_type._meta.verbose_name_plural
 | 
			
		||||
                        )
 | 
			
		||||
                    ),
 | 
			
		||||
                    log_level="info",
 | 
			
		||||
                    logger=f"{provider._meta.verbose_name}@{object_type}",
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        for obj in paginator.page(page).object_list:
 | 
			
		||||
            obj: Model
 | 
			
		||||
            try:
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ from django.urls import include, path
 | 
			
		||||
from authentik.providers.oauth2.views.authorize import AuthorizationFlowInitView
 | 
			
		||||
from authentik.providers.oauth2.views.device_init import DeviceEntryView
 | 
			
		||||
from authentik.providers.oauth2.views.github import GitHubUserTeamsView, GitHubUserView
 | 
			
		||||
from authentik.providers.oauth2.views.provider import ProviderInfoView
 | 
			
		||||
from authentik.providers.oauth2.views.token import TokenView
 | 
			
		||||
 | 
			
		||||
github_urlpatterns = [
 | 
			
		||||
@ -40,4 +41,9 @@ urlpatterns = [
 | 
			
		||||
        ),
 | 
			
		||||
        name="device-login",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        ".well-known/oauth-authorization-server/application/o/<slug:application_slug>/",
 | 
			
		||||
        ProviderInfoView.as_view(),
 | 
			
		||||
        name="providers-oauth2-authorization-server-metadata",
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -166,7 +166,6 @@ class ConnectionToken(ExpiringModel):
 | 
			
		||||
        always_merger.merge(settings, default_settings)
 | 
			
		||||
        always_merger.merge(settings, self.endpoint.provider.settings)
 | 
			
		||||
        always_merger.merge(settings, self.endpoint.settings)
 | 
			
		||||
        always_merger.merge(settings, self.settings)
 | 
			
		||||
 | 
			
		||||
        def mapping_evaluator(mappings: QuerySet):
 | 
			
		||||
            for mapping in mappings:
 | 
			
		||||
@ -191,6 +190,7 @@ class ConnectionToken(ExpiringModel):
 | 
			
		||||
        mapping_evaluator(
 | 
			
		||||
            RACPropertyMapping.objects.filter(endpoint__in=[self.endpoint]).order_by("name")
 | 
			
		||||
        )
 | 
			
		||||
        always_merger.merge(settings, self.settings)
 | 
			
		||||
 | 
			
		||||
        settings["drive-path"] = f"/tmp/connection/{self.token}"  # nosec
 | 
			
		||||
        settings["create-drive-path"] = "true"
 | 
			
		||||
 | 
			
		||||
@ -90,23 +90,6 @@ class TestModels(TransactionTestCase):
 | 
			
		||||
                "resize-method": "display-update",
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        # Set settings in token
 | 
			
		||||
        token.settings = {
 | 
			
		||||
            "level": "token",
 | 
			
		||||
        }
 | 
			
		||||
        token.save()
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            token.get_settings(),
 | 
			
		||||
            {
 | 
			
		||||
                "hostname": self.endpoint.host.split(":")[0],
 | 
			
		||||
                "port": "1324",
 | 
			
		||||
                "client-name": f"authentik - {self.user}",
 | 
			
		||||
                "drive-path": path,
 | 
			
		||||
                "create-drive-path": "true",
 | 
			
		||||
                "level": "token",
 | 
			
		||||
                "resize-method": "display-update",
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        # Set settings in property mapping (provider)
 | 
			
		||||
        mapping = RACPropertyMapping.objects.create(
 | 
			
		||||
            name=generate_id(),
 | 
			
		||||
@ -151,3 +134,22 @@ class TestModels(TransactionTestCase):
 | 
			
		||||
                "resize-method": "display-update",
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        # Set settings in token
 | 
			
		||||
        token.settings = {
 | 
			
		||||
            "level": "token",
 | 
			
		||||
        }
 | 
			
		||||
        token.save()
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            token.get_settings(),
 | 
			
		||||
            {
 | 
			
		||||
                "hostname": self.endpoint.host.split(":")[0],
 | 
			
		||||
                "port": "1324",
 | 
			
		||||
                "client-name": f"authentik - {self.user}",
 | 
			
		||||
                "drive-path": path,
 | 
			
		||||
                "create-drive-path": "true",
 | 
			
		||||
                "foo": "true",
 | 
			
		||||
                "bar": "6",
 | 
			
		||||
                "resize-method": "display-update",
 | 
			
		||||
                "level": "token",
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -47,15 +47,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
 | 
			
		||||
 | 
			
		||||
    def to_schema(self, obj: Group, connection: SCIMProviderGroup) -> SCIMGroupSchema:
 | 
			
		||||
        """Convert authentik user into SCIM"""
 | 
			
		||||
        raw_scim_group = super().to_schema(
 | 
			
		||||
            obj,
 | 
			
		||||
            connection,
 | 
			
		||||
            schemas=(SCIM_GROUP_SCHEMA,),
 | 
			
		||||
        )
 | 
			
		||||
        raw_scim_group = super().to_schema(obj, connection)
 | 
			
		||||
        try:
 | 
			
		||||
            scim_group = SCIMGroupSchema.model_validate(delete_none_values(raw_scim_group))
 | 
			
		||||
        except ValidationError as exc:
 | 
			
		||||
            raise StopSync(exc, obj) from exc
 | 
			
		||||
        if SCIM_GROUP_SCHEMA not in scim_group.schemas:
 | 
			
		||||
            scim_group.schemas.insert(0, SCIM_GROUP_SCHEMA)
 | 
			
		||||
        # As this might be unset, we need to tell pydantic it's set so ensure the schemas
 | 
			
		||||
        # are included, even if its just the defaults
 | 
			
		||||
        scim_group.schemas = list(scim_group.schemas)
 | 
			
		||||
        if not scim_group.externalId:
 | 
			
		||||
            scim_group.externalId = str(obj.pk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -31,15 +31,16 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
 | 
			
		||||
 | 
			
		||||
    def to_schema(self, obj: User, connection: SCIMProviderUser) -> SCIMUserSchema:
 | 
			
		||||
        """Convert authentik user into SCIM"""
 | 
			
		||||
        raw_scim_user = super().to_schema(
 | 
			
		||||
            obj,
 | 
			
		||||
            connection,
 | 
			
		||||
            schemas=(SCIM_USER_SCHEMA,),
 | 
			
		||||
        )
 | 
			
		||||
        raw_scim_user = super().to_schema(obj, connection)
 | 
			
		||||
        try:
 | 
			
		||||
            scim_user = SCIMUserSchema.model_validate(delete_none_values(raw_scim_user))
 | 
			
		||||
        except ValidationError as exc:
 | 
			
		||||
            raise StopSync(exc, obj) from exc
 | 
			
		||||
        if SCIM_USER_SCHEMA not in scim_user.schemas:
 | 
			
		||||
            scim_user.schemas.insert(0, SCIM_USER_SCHEMA)
 | 
			
		||||
        # As this might be unset, we need to tell pydantic it's set so ensure the schemas
 | 
			
		||||
        # are included, even if its just the defaults
 | 
			
		||||
        scim_user.schemas = list(scim_user.schemas)
 | 
			
		||||
        if not scim_user.externalId:
 | 
			
		||||
            scim_user.externalId = str(obj.uid)
 | 
			
		||||
        return scim_user
 | 
			
		||||
 | 
			
		||||
@ -91,6 +91,57 @@ class SCIMUserTests(TestCase):
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @Mocker()
 | 
			
		||||
    def test_user_create_custom_schema(self, mock: Mocker):
 | 
			
		||||
        """Test user creation with custom schema"""
 | 
			
		||||
        schema = SCIMMapping.objects.create(
 | 
			
		||||
            name="custom_schema",
 | 
			
		||||
            expression="""return {"schemas": ["foo"]}""",
 | 
			
		||||
        )
 | 
			
		||||
        self.provider.property_mappings.add(schema)
 | 
			
		||||
        scim_id = generate_id()
 | 
			
		||||
        mock.get(
 | 
			
		||||
            "https://localhost/ServiceProviderConfig",
 | 
			
		||||
            json={},
 | 
			
		||||
        )
 | 
			
		||||
        mock.post(
 | 
			
		||||
            "https://localhost/Users",
 | 
			
		||||
            json={
 | 
			
		||||
                "id": scim_id,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        uid = generate_id()
 | 
			
		||||
        user = User.objects.create(
 | 
			
		||||
            username=uid,
 | 
			
		||||
            name=f"{uid} {uid}",
 | 
			
		||||
            email=f"{uid}@goauthentik.io",
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(mock.call_count, 2)
 | 
			
		||||
        self.assertEqual(mock.request_history[0].method, "GET")
 | 
			
		||||
        self.assertEqual(mock.request_history[1].method, "POST")
 | 
			
		||||
        self.assertJSONEqual(
 | 
			
		||||
            mock.request_history[1].body,
 | 
			
		||||
            {
 | 
			
		||||
                "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User", "foo"],
 | 
			
		||||
                "active": True,
 | 
			
		||||
                "emails": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "primary": True,
 | 
			
		||||
                        "type": "other",
 | 
			
		||||
                        "value": f"{uid}@goauthentik.io",
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "externalId": user.uid,
 | 
			
		||||
                "name": {
 | 
			
		||||
                    "familyName": uid,
 | 
			
		||||
                    "formatted": f"{uid} {uid}",
 | 
			
		||||
                    "givenName": uid,
 | 
			
		||||
                },
 | 
			
		||||
                "displayName": f"{uid} {uid}",
 | 
			
		||||
                "userName": uid,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @Mocker()
 | 
			
		||||
    def test_user_create_different_provider_same_id(self, mock: Mocker):
 | 
			
		||||
        """Test user creation with multiple providers that happen
 | 
			
		||||
@ -384,7 +435,7 @@ class SCIMUserTests(TestCase):
 | 
			
		||||
                self.assertIn(request.method, SAFE_METHODS)
 | 
			
		||||
        task = SystemTask.objects.filter(uid=slugify(self.provider.name)).first()
 | 
			
		||||
        self.assertIsNotNone(task)
 | 
			
		||||
        drop_msg = task.messages[2]
 | 
			
		||||
        drop_msg = task.messages[3]
 | 
			
		||||
        self.assertEqual(drop_msg["event"], "Dropping mutating request due to dry run")
 | 
			
		||||
        self.assertIsNotNone(drop_msg["attributes"]["url"])
 | 
			
		||||
        self.assertIsNotNone(drop_msg["attributes"]["body"])
 | 
			
		||||
 | 
			
		||||
@ -424,7 +424,7 @@ else:
 | 
			
		||||
        "BACKEND": "authentik.root.storages.FileStorage",
 | 
			
		||||
        "OPTIONS": {
 | 
			
		||||
            "location": Path(CONFIG.get("storage.media.file.path")),
 | 
			
		||||
            "base_url": "/media/",
 | 
			
		||||
            "base_url": CONFIG.get("web.path", "/") + "media/",
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    # Compatibility for apps not supporting top-level STORAGES
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,8 @@ class PytestTestRunner(DiscoverRunner):  # pragma: no cover
 | 
			
		||||
 | 
			
		||||
        if kwargs.get("randomly_seed", None):
 | 
			
		||||
            self.args.append(f"--randomly-seed={kwargs['randomly_seed']}")
 | 
			
		||||
        if kwargs.get("no_capture", False):
 | 
			
		||||
            self.args.append("--capture=no")
 | 
			
		||||
 | 
			
		||||
        settings.TEST = True
 | 
			
		||||
        settings.CELERY["task_always_eager"] = True
 | 
			
		||||
@ -64,6 +66,11 @@ class PytestTestRunner(DiscoverRunner):  # pragma: no cover
 | 
			
		||||
            "Default behaviour: use random.Random().getrandbits(32), so the seed is"
 | 
			
		||||
            "different on each run.",
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            "--no-capture",
 | 
			
		||||
            action="store_true",
 | 
			
		||||
            help="Disable any capturing of stdout/stderr during tests.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def run_tests(self, test_labels, extra_tests=None, **kwargs):
 | 
			
		||||
        """Run pytest and return the exitcode.
 | 
			
		||||
 | 
			
		||||
@ -103,6 +103,7 @@ class LDAPSourceSerializer(SourceSerializer):
 | 
			
		||||
            "user_object_filter",
 | 
			
		||||
            "group_object_filter",
 | 
			
		||||
            "group_membership_field",
 | 
			
		||||
            "user_membership_attribute",
 | 
			
		||||
            "object_uniqueness_field",
 | 
			
		||||
            "password_login_update_internal_password",
 | 
			
		||||
            "sync_users",
 | 
			
		||||
@ -111,6 +112,7 @@ class LDAPSourceSerializer(SourceSerializer):
 | 
			
		||||
            "sync_parent_group",
 | 
			
		||||
            "connectivity",
 | 
			
		||||
            "lookup_groups_from_user",
 | 
			
		||||
            "delete_not_found_objects",
 | 
			
		||||
        ]
 | 
			
		||||
        extra_kwargs = {"bind_password": {"write_only": True}}
 | 
			
		||||
 | 
			
		||||
@ -138,6 +140,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
 | 
			
		||||
        "user_object_filter",
 | 
			
		||||
        "group_object_filter",
 | 
			
		||||
        "group_membership_field",
 | 
			
		||||
        "user_membership_attribute",
 | 
			
		||||
        "object_uniqueness_field",
 | 
			
		||||
        "password_login_update_internal_password",
 | 
			
		||||
        "sync_users",
 | 
			
		||||
@ -147,6 +150,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
 | 
			
		||||
        "user_property_mappings",
 | 
			
		||||
        "group_property_mappings",
 | 
			
		||||
        "lookup_groups_from_user",
 | 
			
		||||
        "delete_not_found_objects",
 | 
			
		||||
    ]
 | 
			
		||||
    search_fields = ["name", "slug"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,48 @@
 | 
			
		||||
# Generated by Django 5.1.9 on 2025-05-28 08:15
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("authentik_core", "0048_delete_oldauthenticatedsession_content_type"),
 | 
			
		||||
        ("authentik_sources_ldap", "0008_groupldapsourceconnection_userldapsourceconnection"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="groupldapsourceconnection",
 | 
			
		||||
            name="validated_by",
 | 
			
		||||
            field=models.UUIDField(
 | 
			
		||||
                blank=True,
 | 
			
		||||
                help_text="Unique ID used while checking if this object still exists in the directory.",
 | 
			
		||||
                null=True,
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="delete_not_found_objects",
 | 
			
		||||
            field=models.BooleanField(
 | 
			
		||||
                default=False,
 | 
			
		||||
                help_text="Delete authentik users and groups which were previously supplied by this source, but are now missing from it.",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="userldapsourceconnection",
 | 
			
		||||
            name="validated_by",
 | 
			
		||||
            field=models.UUIDField(
 | 
			
		||||
                blank=True,
 | 
			
		||||
                help_text="Unique ID used while checking if this object still exists in the directory.",
 | 
			
		||||
                null=True,
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddIndex(
 | 
			
		||||
            model_name="groupldapsourceconnection",
 | 
			
		||||
            index=models.Index(fields=["validated_by"], name="authentik_s_validat_b70447_idx"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddIndex(
 | 
			
		||||
            model_name="userldapsourceconnection",
 | 
			
		||||
            index=models.Index(fields=["validated_by"], name="authentik_s_validat_ff2ebc_idx"),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
# Generated by Django 5.1.9 on 2025-05-29 11:22
 | 
			
		||||
 | 
			
		||||
from django.apps.registry import Apps
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_user_membership_attribute(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
 | 
			
		||||
    LDAPSource = apps.get_model("authentik_sources_ldap", "LDAPSource")
 | 
			
		||||
    db_alias = schema_editor.connection.alias
 | 
			
		||||
 | 
			
		||||
    LDAPSource.objects.using(db_alias).filter(group_membership_field="memberUid").all().update(
 | 
			
		||||
        user_membership_attribute="ldap_uniq"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("authentik_sources_ldap", "0009_groupldapsourceconnection_validated_by_and_more"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="user_membership_attribute",
 | 
			
		||||
            field=models.TextField(
 | 
			
		||||
                default="distinguishedName",
 | 
			
		||||
                help_text="Attribute which matches the value of `group_membership_field`.",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RunPython(set_user_membership_attribute, migrations.RunPython.noop),
 | 
			
		||||
    ]
 | 
			
		||||
@ -100,6 +100,10 @@ class LDAPSource(Source):
 | 
			
		||||
        default="(objectClass=person)",
 | 
			
		||||
        help_text=_("Consider Objects matching this filter to be Users."),
 | 
			
		||||
    )
 | 
			
		||||
    user_membership_attribute = models.TextField(
 | 
			
		||||
        default=LDAP_DISTINGUISHED_NAME,
 | 
			
		||||
        help_text=_("Attribute which matches the value of `group_membership_field`."),
 | 
			
		||||
    )
 | 
			
		||||
    group_membership_field = models.TextField(
 | 
			
		||||
        default="member", help_text=_("Field which contains members of a group.")
 | 
			
		||||
    )
 | 
			
		||||
@ -137,6 +141,14 @@ class LDAPSource(Source):
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    delete_not_found_objects = models.BooleanField(
 | 
			
		||||
        default=False,
 | 
			
		||||
        help_text=_(
 | 
			
		||||
            "Delete authentik users and groups which were previously supplied by this source, "
 | 
			
		||||
            "but are now missing from it."
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def component(self) -> str:
 | 
			
		||||
        return "ak-source-ldap-form"
 | 
			
		||||
@ -321,6 +333,12 @@ class LDAPSourcePropertyMapping(PropertyMapping):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserLDAPSourceConnection(UserSourceConnection):
 | 
			
		||||
    validated_by = models.UUIDField(
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
        help_text=_("Unique ID used while checking if this object still exists in the directory."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def serializer(self) -> type[Serializer]:
 | 
			
		||||
        from authentik.sources.ldap.api import (
 | 
			
		||||
@ -332,9 +350,18 @@ class UserLDAPSourceConnection(UserSourceConnection):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("User LDAP Source Connection")
 | 
			
		||||
        verbose_name_plural = _("User LDAP Source Connections")
 | 
			
		||||
        indexes = [
 | 
			
		||||
            models.Index(fields=["validated_by"]),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GroupLDAPSourceConnection(GroupSourceConnection):
 | 
			
		||||
    validated_by = models.UUIDField(
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
        help_text=_("Unique ID used while checking if this object still exists in the directory."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def serializer(self) -> type[Serializer]:
 | 
			
		||||
        from authentik.sources.ldap.api import (
 | 
			
		||||
@ -346,3 +373,6 @@ class GroupLDAPSourceConnection(GroupSourceConnection):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("Group LDAP Source Connection")
 | 
			
		||||
        verbose_name_plural = _("Group LDAP Source Connections")
 | 
			
		||||
        indexes = [
 | 
			
		||||
            models.Index(fields=["validated_by"]),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ from structlog.stdlib import BoundLogger, get_logger
 | 
			
		||||
from authentik.core.sources.mapper import SourceMapper
 | 
			
		||||
from authentik.lib.config import CONFIG
 | 
			
		||||
from authentik.lib.sync.mapper import PropertyMappingManager
 | 
			
		||||
from authentik.sources.ldap.models import LDAPSource
 | 
			
		||||
from authentik.sources.ldap.models import LDAPSource, flatten
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseLDAPSynchronizer:
 | 
			
		||||
@ -77,6 +77,16 @@ class BaseLDAPSynchronizer:
 | 
			
		||||
        """Get objects from LDAP, implemented in subclass"""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def get_attributes(self, object):
 | 
			
		||||
        if "attributes" not in object:
 | 
			
		||||
            return
 | 
			
		||||
        return object.get("attributes", {})
 | 
			
		||||
 | 
			
		||||
    def get_identifier(self, attributes: dict):
 | 
			
		||||
        if not attributes.get(self._source.object_uniqueness_field):
 | 
			
		||||
            return
 | 
			
		||||
        return flatten(attributes[self._source.object_uniqueness_field])
 | 
			
		||||
 | 
			
		||||
    def search_paginator(  # noqa: PLR0913
 | 
			
		||||
        self,
 | 
			
		||||
        search_base,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										61
									
								
								authentik/sources/ldap/sync/forward_delete_groups.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								authentik/sources/ldap/sync/forward_delete_groups.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
from collections.abc import Generator
 | 
			
		||||
from itertools import batched
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
from ldap3 import SUBTREE
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import Group
 | 
			
		||||
from authentik.sources.ldap.models import GroupLDAPSourceConnection
 | 
			
		||||
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
 | 
			
		||||
from authentik.sources.ldap.sync.forward_delete_users import DELETE_CHUNK_SIZE, UPDATE_CHUNK_SIZE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GroupLDAPForwardDeletion(BaseLDAPSynchronizer):
 | 
			
		||||
    """Delete LDAP Groups from authentik"""
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def name() -> str:
 | 
			
		||||
        return "group_deletions"
 | 
			
		||||
 | 
			
		||||
    def get_objects(self, **kwargs) -> Generator:
 | 
			
		||||
        if not self._source.sync_groups or not self._source.delete_not_found_objects:
 | 
			
		||||
            self.message("Group syncing is disabled for this Source")
 | 
			
		||||
            return iter(())
 | 
			
		||||
 | 
			
		||||
        uuid = uuid4()
 | 
			
		||||
        groups = self._source.connection().extend.standard.paged_search(
 | 
			
		||||
            search_base=self.base_dn_groups,
 | 
			
		||||
            search_filter=self._source.group_object_filter,
 | 
			
		||||
            search_scope=SUBTREE,
 | 
			
		||||
            attributes=[self._source.object_uniqueness_field],
 | 
			
		||||
            generator=True,
 | 
			
		||||
            **kwargs,
 | 
			
		||||
        )
 | 
			
		||||
        for batch in batched(groups, UPDATE_CHUNK_SIZE, strict=False):
 | 
			
		||||
            identifiers = []
 | 
			
		||||
            for group in batch:
 | 
			
		||||
                if not (attributes := self.get_attributes(group)):
 | 
			
		||||
                    continue
 | 
			
		||||
                if identifier := self.get_identifier(attributes):
 | 
			
		||||
                    identifiers.append(identifier)
 | 
			
		||||
            GroupLDAPSourceConnection.objects.filter(identifier__in=identifiers).update(
 | 
			
		||||
                validated_by=uuid
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return batched(
 | 
			
		||||
            GroupLDAPSourceConnection.objects.filter(source=self._source)
 | 
			
		||||
            .exclude(validated_by=uuid)
 | 
			
		||||
            .values_list("group", flat=True)
 | 
			
		||||
            .iterator(chunk_size=DELETE_CHUNK_SIZE),
 | 
			
		||||
            DELETE_CHUNK_SIZE,
 | 
			
		||||
            strict=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def sync(self, group_pks: tuple) -> int:
 | 
			
		||||
        """Delete authentik groups"""
 | 
			
		||||
        if not self._source.sync_groups or not self._source.delete_not_found_objects:
 | 
			
		||||
            self.message("Group syncing is disabled for this Source")
 | 
			
		||||
            return -1
 | 
			
		||||
        self._logger.debug("Deleting groups", group_pks=group_pks)
 | 
			
		||||
        _, deleted_per_type = Group.objects.filter(pk__in=group_pks).delete()
 | 
			
		||||
        return deleted_per_type.get(Group._meta.label, 0)
 | 
			
		||||
							
								
								
									
										63
									
								
								authentik/sources/ldap/sync/forward_delete_users.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								authentik/sources/ldap/sync/forward_delete_users.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
from collections.abc import Generator
 | 
			
		||||
from itertools import batched
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
from ldap3 import SUBTREE
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.sources.ldap.models import UserLDAPSourceConnection
 | 
			
		||||
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
 | 
			
		||||
 | 
			
		||||
UPDATE_CHUNK_SIZE = 10_000
 | 
			
		||||
DELETE_CHUNK_SIZE = 50
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserLDAPForwardDeletion(BaseLDAPSynchronizer):
 | 
			
		||||
    """Delete LDAP Users from authentik"""
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def name() -> str:
 | 
			
		||||
        return "user_deletions"
 | 
			
		||||
 | 
			
		||||
    def get_objects(self, **kwargs) -> Generator:
 | 
			
		||||
        if not self._source.sync_users or not self._source.delete_not_found_objects:
 | 
			
		||||
            self.message("User syncing is disabled for this Source")
 | 
			
		||||
            return iter(())
 | 
			
		||||
 | 
			
		||||
        uuid = uuid4()
 | 
			
		||||
        users = self._source.connection().extend.standard.paged_search(
 | 
			
		||||
            search_base=self.base_dn_users,
 | 
			
		||||
            search_filter=self._source.user_object_filter,
 | 
			
		||||
            search_scope=SUBTREE,
 | 
			
		||||
            attributes=[self._source.object_uniqueness_field],
 | 
			
		||||
            generator=True,
 | 
			
		||||
            **kwargs,
 | 
			
		||||
        )
 | 
			
		||||
        for batch in batched(users, UPDATE_CHUNK_SIZE, strict=False):
 | 
			
		||||
            identifiers = []
 | 
			
		||||
            for user in batch:
 | 
			
		||||
                if not (attributes := self.get_attributes(user)):
 | 
			
		||||
                    continue
 | 
			
		||||
                if identifier := self.get_identifier(attributes):
 | 
			
		||||
                    identifiers.append(identifier)
 | 
			
		||||
            UserLDAPSourceConnection.objects.filter(identifier__in=identifiers).update(
 | 
			
		||||
                validated_by=uuid
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return batched(
 | 
			
		||||
            UserLDAPSourceConnection.objects.filter(source=self._source)
 | 
			
		||||
            .exclude(validated_by=uuid)
 | 
			
		||||
            .values_list("user", flat=True)
 | 
			
		||||
            .iterator(chunk_size=DELETE_CHUNK_SIZE),
 | 
			
		||||
            DELETE_CHUNK_SIZE,
 | 
			
		||||
            strict=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def sync(self, user_pks: tuple) -> int:
 | 
			
		||||
        """Delete authentik users"""
 | 
			
		||||
        if not self._source.sync_users or not self._source.delete_not_found_objects:
 | 
			
		||||
            self.message("User syncing is disabled for this Source")
 | 
			
		||||
            return -1
 | 
			
		||||
        self._logger.debug("Deleting users", user_pks=user_pks)
 | 
			
		||||
        _, deleted_per_type = User.objects.filter(pk__in=user_pks).delete()
 | 
			
		||||
        return deleted_per_type.get(User._meta.label, 0)
 | 
			
		||||
@ -58,18 +58,16 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
 | 
			
		||||
            return -1
 | 
			
		||||
        group_count = 0
 | 
			
		||||
        for group in page_data:
 | 
			
		||||
            if "attributes" not in group:
 | 
			
		||||
            if (attributes := self.get_attributes(group)) is None:
 | 
			
		||||
                continue
 | 
			
		||||
            attributes = group.get("attributes", {})
 | 
			
		||||
            group_dn = flatten(flatten(group.get("entryDN", group.get("dn"))))
 | 
			
		||||
            if not attributes.get(self._source.object_uniqueness_field):
 | 
			
		||||
            if not (uniq := self.get_identifier(attributes)):
 | 
			
		||||
                self.message(
 | 
			
		||||
                    f"Uniqueness field not found/not set in attributes: '{group_dn}'",
 | 
			
		||||
                    attributes=attributes.keys(),
 | 
			
		||||
                    dn=group_dn,
 | 
			
		||||
                )
 | 
			
		||||
                continue
 | 
			
		||||
            uniq = flatten(attributes[self._source.object_uniqueness_field])
 | 
			
		||||
            try:
 | 
			
		||||
                defaults = {
 | 
			
		||||
                    k: flatten(v)
 | 
			
		||||
 | 
			
		||||
@ -63,25 +63,19 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
 | 
			
		||||
                    group_member_dn = group_member.get("dn", {})
 | 
			
		||||
                    members.append(group_member_dn)
 | 
			
		||||
            else:
 | 
			
		||||
                if "attributes" not in group:
 | 
			
		||||
                if (attributes := self.get_attributes(group)) is None:
 | 
			
		||||
                    continue
 | 
			
		||||
                members = group.get("attributes", {}).get(self._source.group_membership_field, [])
 | 
			
		||||
                members = attributes.get(self._source.group_membership_field, [])
 | 
			
		||||
 | 
			
		||||
            ak_group = self.get_group(group)
 | 
			
		||||
            if not ak_group:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            membership_mapping_attribute = LDAP_DISTINGUISHED_NAME
 | 
			
		||||
            if self._source.group_membership_field == "memberUid":
 | 
			
		||||
                # If memberships are based on the posixGroup's 'memberUid'
 | 
			
		||||
                # attribute we use the RDN instead of the FDN to lookup members.
 | 
			
		||||
                membership_mapping_attribute = LDAP_UNIQUENESS
 | 
			
		||||
 | 
			
		||||
            users = User.objects.filter(
 | 
			
		||||
                Q(**{f"attributes__{membership_mapping_attribute}__in": members})
 | 
			
		||||
                Q(**{f"attributes__{self._source.user_membership_attribute}__in": members})
 | 
			
		||||
                | Q(
 | 
			
		||||
                    **{
 | 
			
		||||
                        f"attributes__{membership_mapping_attribute}__isnull": True,
 | 
			
		||||
                        f"attributes__{self._source.user_membership_attribute}__isnull": True,
 | 
			
		||||
                        "ak_groups__in": [ak_group],
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
@ -60,18 +60,16 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
 | 
			
		||||
            return -1
 | 
			
		||||
        user_count = 0
 | 
			
		||||
        for user in page_data:
 | 
			
		||||
            if "attributes" not in user:
 | 
			
		||||
            if (attributes := self.get_attributes(user)) is None:
 | 
			
		||||
                continue
 | 
			
		||||
            attributes = user.get("attributes", {})
 | 
			
		||||
            user_dn = flatten(user.get("entryDN", user.get("dn")))
 | 
			
		||||
            if not attributes.get(self._source.object_uniqueness_field):
 | 
			
		||||
            if not (uniq := self.get_identifier(attributes)):
 | 
			
		||||
                self.message(
 | 
			
		||||
                    f"Uniqueness field not found/not set in attributes: '{user_dn}'",
 | 
			
		||||
                    attributes=attributes.keys(),
 | 
			
		||||
                    dn=user_dn,
 | 
			
		||||
                )
 | 
			
		||||
                continue
 | 
			
		||||
            uniq = flatten(attributes[self._source.object_uniqueness_field])
 | 
			
		||||
            try:
 | 
			
		||||
                defaults = {
 | 
			
		||||
                    k: flatten(v)
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,8 @@ from authentik.lib.utils.reflection import class_to_path, path_to_class
 | 
			
		||||
from authentik.root.celery import CELERY_APP
 | 
			
		||||
from authentik.sources.ldap.models import LDAPSource
 | 
			
		||||
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
 | 
			
		||||
from authentik.sources.ldap.sync.forward_delete_groups import GroupLDAPForwardDeletion
 | 
			
		||||
from authentik.sources.ldap.sync.forward_delete_users import UserLDAPForwardDeletion
 | 
			
		||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
 | 
			
		||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
 | 
			
		||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
 | 
			
		||||
@ -52,11 +54,11 @@ def ldap_connectivity_check(pk: str | None = None):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@CELERY_APP.task(
 | 
			
		||||
    # We take the configured hours timeout time by 2.5 as we run user and
 | 
			
		||||
    # group in parallel and then membership, so 2x is to cover the serial tasks,
 | 
			
		||||
    # We take the configured hours timeout time by 3.5 as we run user and
 | 
			
		||||
    # group in parallel and then membership, then deletions, so 3x is to cover the serial tasks,
 | 
			
		||||
    # and 0.5x on top of that to give some more leeway
 | 
			
		||||
    soft_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 2.5,
 | 
			
		||||
    task_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 2.5,
 | 
			
		||||
    soft_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 3.5,
 | 
			
		||||
    task_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 3.5,
 | 
			
		||||
)
 | 
			
		||||
def ldap_sync_single(source_pk: str):
 | 
			
		||||
    """Sync a single source"""
 | 
			
		||||
@ -79,6 +81,25 @@ def ldap_sync_single(source_pk: str):
 | 
			
		||||
            group(
 | 
			
		||||
                ldap_sync_paginator(source, MembershipLDAPSynchronizer),
 | 
			
		||||
            ),
 | 
			
		||||
            # Finally, deletions. What we'd really like to do here is something like
 | 
			
		||||
            # ```
 | 
			
		||||
            # user_identifiers = <ldap query>
 | 
			
		||||
            # User.objects.exclude(
 | 
			
		||||
            #     usersourceconnection__identifier__in=user_uniqueness_identifiers,
 | 
			
		||||
            # ).delete()
 | 
			
		||||
            # ```
 | 
			
		||||
            # This runs into performance issues in large installations. So instead we spread the
 | 
			
		||||
            # work out into three steps:
 | 
			
		||||
            # 1. Get every object from the LDAP source.
 | 
			
		||||
            # 2. Mark every object as "safe" in the database. This is quick, but any error could
 | 
			
		||||
            #    mean deleting users which should not be deleted, so we do it immediately, in
 | 
			
		||||
            #    large chunks, and only queue the deletion step afterwards.
 | 
			
		||||
            # 3. Delete every unmarked item. This is slow, so we spread it over many tasks in
 | 
			
		||||
            #    small chunks.
 | 
			
		||||
            group(
 | 
			
		||||
                ldap_sync_paginator(source, UserLDAPForwardDeletion)
 | 
			
		||||
                + ldap_sync_paginator(source, GroupLDAPForwardDeletion),
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        task()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,33 @@
 | 
			
		||||
 | 
			
		||||
from ldap3 import MOCK_SYNC, OFFLINE_SLAPD_2_4, Connection, Server
 | 
			
		||||
 | 
			
		||||
# The mock modifies these in place, so we have to define them per string
 | 
			
		||||
user_in_slapd_dn = "cn=user_in_slapd_cn,ou=users,dc=goauthentik,dc=io"
 | 
			
		||||
user_in_slapd_cn = "user_in_slapd_cn"
 | 
			
		||||
user_in_slapd_uid = "user_in_slapd_uid"
 | 
			
		||||
user_in_slapd_object_class = "person"
 | 
			
		||||
user_in_slapd = {
 | 
			
		||||
    "dn": user_in_slapd_dn,
 | 
			
		||||
    "attributes": {
 | 
			
		||||
        "cn": user_in_slapd_cn,
 | 
			
		||||
        "uid": user_in_slapd_uid,
 | 
			
		||||
        "objectClass": user_in_slapd_object_class,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
group_in_slapd_dn = "cn=user_in_slapd_cn,ou=groups,dc=goauthentik,dc=io"
 | 
			
		||||
group_in_slapd_cn = "group_in_slapd_cn"
 | 
			
		||||
group_in_slapd_uid = "group_in_slapd_uid"
 | 
			
		||||
group_in_slapd_object_class = "groupOfNames"
 | 
			
		||||
group_in_slapd = {
 | 
			
		||||
    "dn": group_in_slapd_dn,
 | 
			
		||||
    "attributes": {
 | 
			
		||||
        "cn": group_in_slapd_cn,
 | 
			
		||||
        "uid": group_in_slapd_uid,
 | 
			
		||||
        "objectClass": group_in_slapd_object_class,
 | 
			
		||||
        "member": [user_in_slapd["dn"]],
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def mock_slapd_connection(password: str) -> Connection:
 | 
			
		||||
    """Create mock SLAPD connection"""
 | 
			
		||||
@ -96,5 +123,14 @@ def mock_slapd_connection(password: str) -> Connection:
 | 
			
		||||
            "objectClass": "posixAccount",
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    # Known user and group
 | 
			
		||||
    connection.strategy.add_entry(
 | 
			
		||||
        user_in_slapd["dn"],
 | 
			
		||||
        user_in_slapd["attributes"],
 | 
			
		||||
    )
 | 
			
		||||
    connection.strategy.add_entry(
 | 
			
		||||
        group_in_slapd["dn"],
 | 
			
		||||
        group_in_slapd["attributes"],
 | 
			
		||||
    )
 | 
			
		||||
    connection.bind()
 | 
			
		||||
    return connection
 | 
			
		||||
 | 
			
		||||
@ -13,14 +13,26 @@ from authentik.events.system_tasks import TaskStatus
 | 
			
		||||
from authentik.lib.generators import generate_id, generate_key
 | 
			
		||||
from authentik.lib.sync.outgoing.exceptions import StopSync
 | 
			
		||||
from authentik.lib.utils.reflection import class_to_path
 | 
			
		||||
from authentik.sources.ldap.models import LDAPSource, LDAPSourcePropertyMapping
 | 
			
		||||
from authentik.sources.ldap.models import (
 | 
			
		||||
    GroupLDAPSourceConnection,
 | 
			
		||||
    LDAPSource,
 | 
			
		||||
    LDAPSourcePropertyMapping,
 | 
			
		||||
    UserLDAPSourceConnection,
 | 
			
		||||
)
 | 
			
		||||
from authentik.sources.ldap.sync.forward_delete_users import DELETE_CHUNK_SIZE
 | 
			
		||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
 | 
			
		||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
 | 
			
		||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
 | 
			
		||||
from authentik.sources.ldap.tasks import ldap_sync, ldap_sync_all
 | 
			
		||||
from authentik.sources.ldap.tests.mock_ad import mock_ad_connection
 | 
			
		||||
from authentik.sources.ldap.tests.mock_freeipa import mock_freeipa_connection
 | 
			
		||||
from authentik.sources.ldap.tests.mock_slapd import mock_slapd_connection
 | 
			
		||||
from authentik.sources.ldap.tests.mock_slapd import (
 | 
			
		||||
    group_in_slapd_cn,
 | 
			
		||||
    group_in_slapd_uid,
 | 
			
		||||
    mock_slapd_connection,
 | 
			
		||||
    user_in_slapd_cn,
 | 
			
		||||
    user_in_slapd_uid,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
LDAP_PASSWORD = generate_key()
 | 
			
		||||
 | 
			
		||||
@ -257,12 +269,56 @@ class LDAPSyncTests(TestCase):
 | 
			
		||||
        self.source.group_membership_field = "memberUid"
 | 
			
		||||
        self.source.user_object_filter = "(objectClass=posixAccount)"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=posixGroup)"
 | 
			
		||||
        self.source.user_membership_attribute = "uid"
 | 
			
		||||
        self.source.user_property_mappings.set(
 | 
			
		||||
            [
 | 
			
		||||
                *LDAPSourcePropertyMapping.objects.filter(
 | 
			
		||||
                    Q(managed__startswith="goauthentik.io/sources/ldap/default")
 | 
			
		||||
                    | Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
 | 
			
		||||
                ).all(),
 | 
			
		||||
                LDAPSourcePropertyMapping.objects.create(
 | 
			
		||||
                    name="name",
 | 
			
		||||
                    expression='return {"attributes": {"uid": list_flatten(ldap.get("uid"))}}',
 | 
			
		||||
                ),
 | 
			
		||||
            ]
 | 
			
		||||
        )
 | 
			
		||||
        self.source.group_property_mappings.set(
 | 
			
		||||
            LDAPSourcePropertyMapping.objects.filter(
 | 
			
		||||
                Q(managed__startswith="goauthentik.io/sources/ldap/default")
 | 
			
		||||
                | Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
 | 
			
		||||
                managed="goauthentik.io/sources/ldap/openldap-cn"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            self.source.save()
 | 
			
		||||
            user_sync = UserLDAPSynchronizer(self.source)
 | 
			
		||||
            user_sync.sync_full()
 | 
			
		||||
            group_sync = GroupLDAPSynchronizer(self.source)
 | 
			
		||||
            group_sync.sync_full()
 | 
			
		||||
            membership_sync = MembershipLDAPSynchronizer(self.source)
 | 
			
		||||
            membership_sync.sync_full()
 | 
			
		||||
            # Test if membership mapping based on memberUid works.
 | 
			
		||||
            posix_group = Group.objects.filter(name="group-posix").first()
 | 
			
		||||
            self.assertTrue(posix_group.users.filter(name="user-posix").exists())
 | 
			
		||||
 | 
			
		||||
    def test_sync_groups_openldap_posix_group_nonstandard_membership_attribute(self):
 | 
			
		||||
        """Test posix group sync"""
 | 
			
		||||
        self.source.object_uniqueness_field = "cn"
 | 
			
		||||
        self.source.group_membership_field = "memberUid"
 | 
			
		||||
        self.source.user_object_filter = "(objectClass=posixAccount)"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=posixGroup)"
 | 
			
		||||
        self.source.user_membership_attribute = "cn"
 | 
			
		||||
        self.source.user_property_mappings.set(
 | 
			
		||||
            [
 | 
			
		||||
                *LDAPSourcePropertyMapping.objects.filter(
 | 
			
		||||
                    Q(managed__startswith="goauthentik.io/sources/ldap/default")
 | 
			
		||||
                    | Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
 | 
			
		||||
                ).all(),
 | 
			
		||||
                LDAPSourcePropertyMapping.objects.create(
 | 
			
		||||
                    name="name",
 | 
			
		||||
                    expression='return {"attributes": {"cn": list_flatten(ldap.get("cn"))}}',
 | 
			
		||||
                ),
 | 
			
		||||
            ]
 | 
			
		||||
        )
 | 
			
		||||
        self.source.group_property_mappings.set(
 | 
			
		||||
            LDAPSourcePropertyMapping.objects.filter(
 | 
			
		||||
                managed="goauthentik.io/sources/ldap/openldap-cn"
 | 
			
		||||
@ -308,3 +364,160 @@ class LDAPSyncTests(TestCase):
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
 | 
			
		||||
    def test_user_deletion(self):
 | 
			
		||||
        """Test user deletion"""
 | 
			
		||||
        user = User.objects.create_user(username="not-in-the-source")
 | 
			
		||||
        UserLDAPSourceConnection.objects.create(
 | 
			
		||||
            user=user, source=self.source, identifier="not-in-the-source"
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.delete_not_found_objects = True
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertFalse(User.objects.filter(username="not-in-the-source").exists())
 | 
			
		||||
 | 
			
		||||
    def test_user_deletion_still_in_source(self):
 | 
			
		||||
        """Test that user is not deleted if it's still in the source"""
 | 
			
		||||
        username = user_in_slapd_cn
 | 
			
		||||
        identifier = user_in_slapd_uid
 | 
			
		||||
        user = User.objects.create_user(username=username)
 | 
			
		||||
        UserLDAPSourceConnection.objects.create(
 | 
			
		||||
            user=user, source=self.source, identifier=identifier
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.delete_not_found_objects = True
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertTrue(User.objects.filter(username=username).exists())
 | 
			
		||||
 | 
			
		||||
    def test_user_deletion_no_sync(self):
 | 
			
		||||
        """Test that user is not deleted if sync_users is False"""
 | 
			
		||||
        user = User.objects.create_user(username="not-in-the-source")
 | 
			
		||||
        UserLDAPSourceConnection.objects.create(
 | 
			
		||||
            user=user, source=self.source, identifier="not-in-the-source"
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.delete_not_found_objects = True
 | 
			
		||||
        self.source.sync_users = False
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertTrue(User.objects.filter(username="not-in-the-source").exists())
 | 
			
		||||
 | 
			
		||||
    def test_user_deletion_no_delete(self):
 | 
			
		||||
        """Test that user is not deleted if delete_not_found_objects is False"""
 | 
			
		||||
        user = User.objects.create_user(username="not-in-the-source")
 | 
			
		||||
        UserLDAPSourceConnection.objects.create(
 | 
			
		||||
            user=user, source=self.source, identifier="not-in-the-source"
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertTrue(User.objects.filter(username="not-in-the-source").exists())
 | 
			
		||||
 | 
			
		||||
    def test_group_deletion(self):
 | 
			
		||||
        """Test group deletion"""
 | 
			
		||||
        group = Group.objects.create(name="not-in-the-source")
 | 
			
		||||
        GroupLDAPSourceConnection.objects.create(
 | 
			
		||||
            group=group, source=self.source, identifier="not-in-the-source"
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.delete_not_found_objects = True
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertFalse(Group.objects.filter(name="not-in-the-source").exists())
 | 
			
		||||
 | 
			
		||||
    def test_group_deletion_still_in_source(self):
 | 
			
		||||
        """Test that group is not deleted if it's still in the source"""
 | 
			
		||||
        groupname = group_in_slapd_cn
 | 
			
		||||
        identifier = group_in_slapd_uid
 | 
			
		||||
        group = Group.objects.create(name=groupname)
 | 
			
		||||
        GroupLDAPSourceConnection.objects.create(
 | 
			
		||||
            group=group, source=self.source, identifier=identifier
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.delete_not_found_objects = True
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertTrue(Group.objects.filter(name=groupname).exists())
 | 
			
		||||
 | 
			
		||||
    def test_group_deletion_no_sync(self):
 | 
			
		||||
        """Test that group is not deleted if sync_groups is False"""
 | 
			
		||||
        group = Group.objects.create(name="not-in-the-source")
 | 
			
		||||
        GroupLDAPSourceConnection.objects.create(
 | 
			
		||||
            group=group, source=self.source, identifier="not-in-the-source"
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.delete_not_found_objects = True
 | 
			
		||||
        self.source.sync_groups = False
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertTrue(Group.objects.filter(name="not-in-the-source").exists())
 | 
			
		||||
 | 
			
		||||
    def test_group_deletion_no_delete(self):
 | 
			
		||||
        """Test that group is not deleted if delete_not_found_objects is False"""
 | 
			
		||||
        group = Group.objects.create(name="not-in-the-source")
 | 
			
		||||
        GroupLDAPSourceConnection.objects.create(
 | 
			
		||||
            group=group, source=self.source, identifier="not-in-the-source"
 | 
			
		||||
        )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
        self.assertTrue(Group.objects.filter(name="not-in-the-source").exists())
 | 
			
		||||
 | 
			
		||||
    def test_batch_deletion(self):
 | 
			
		||||
        """Test batch deletion"""
 | 
			
		||||
        BATCH_SIZE = DELETE_CHUNK_SIZE + 1
 | 
			
		||||
        for i in range(BATCH_SIZE):
 | 
			
		||||
            user = User.objects.create_user(username=f"not-in-the-source-{i}")
 | 
			
		||||
            group = Group.objects.create(name=f"not-in-the-source-{i}")
 | 
			
		||||
            group.users.add(user)
 | 
			
		||||
            UserLDAPSourceConnection.objects.create(
 | 
			
		||||
                user=user, source=self.source, identifier=f"not-in-the-source-{i}-user"
 | 
			
		||||
            )
 | 
			
		||||
            GroupLDAPSourceConnection.objects.create(
 | 
			
		||||
                group=group, source=self.source, identifier=f"not-in-the-source-{i}-group"
 | 
			
		||||
            )
 | 
			
		||||
        self.source.object_uniqueness_field = "uid"
 | 
			
		||||
        self.source.group_object_filter = "(objectClass=groupOfNames)"
 | 
			
		||||
        self.source.delete_not_found_objects = True
 | 
			
		||||
        self.source.save()
 | 
			
		||||
 | 
			
		||||
        connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
 | 
			
		||||
        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
 | 
			
		||||
            ldap_sync_all.delay().get()
 | 
			
		||||
 | 
			
		||||
        self.assertFalse(User.objects.filter(username__startswith="not-in-the-source").exists())
 | 
			
		||||
        self.assertFalse(Group.objects.filter(name__startswith="not-in-the-source").exists())
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ from django.http.response import HttpResponseBadRequest
 | 
			
		||||
from django.shortcuts import get_object_or_404, redirect
 | 
			
		||||
from django.utils.decorators import method_decorator
 | 
			
		||||
from django.utils.http import urlencode
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.views.decorators.csrf import csrf_exempt
 | 
			
		||||
from structlog.stdlib import get_logger
 | 
			
		||||
@ -128,7 +129,9 @@ class InitiateView(View):
 | 
			
		||||
        # otherwise we default to POST_AUTO, with direct redirect
 | 
			
		||||
        if source.binding_type == SAMLBindingTypes.POST:
 | 
			
		||||
            injected_stages.append(in_memory_stage(ConsentStageView))
 | 
			
		||||
            plan_kwargs[PLAN_CONTEXT_CONSENT_HEADER] = f"Continue to {source.name}"
 | 
			
		||||
            plan_kwargs[PLAN_CONTEXT_CONSENT_HEADER] = _(
 | 
			
		||||
                "Continue to {source_name}".format(source_name=source.name)
 | 
			
		||||
            )
 | 
			
		||||
        injected_stages.append(in_memory_stage(AutosubmitStageView))
 | 
			
		||||
        return self.handle_login_flow(
 | 
			
		||||
            source,
 | 
			
		||||
 | 
			
		||||
@ -168,13 +168,10 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase):
 | 
			
		||||
            user__pk=self.user.pk,
 | 
			
		||||
        ).first()
 | 
			
		||||
        self.assertIsNotNone(event)
 | 
			
		||||
        context = dict(event.context)
 | 
			
		||||
        # The auth_method field is being obfuscated as it's marked as sensitive in Django 5.2
 | 
			
		||||
        auth_method = context.pop("auth_method")
 | 
			
		||||
        self.assertIn(auth_method, ["auth_mfa", "********************"])
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            context,
 | 
			
		||||
            event.context,
 | 
			
		||||
            {
 | 
			
		||||
                "auth_method": "auth_mfa",
 | 
			
		||||
                "auth_method_args": {
 | 
			
		||||
                    "mfa_devices": [
 | 
			
		||||
                        {
 | 
			
		||||
 | 
			
		||||
@ -151,9 +151,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
 | 
			
		||||
            webauthn_user_verification=UserVerification.PREFERRED,
 | 
			
		||||
        )
 | 
			
		||||
        stage.webauthn_allowed_device_types.set(
 | 
			
		||||
            WebAuthnDeviceType.objects.filter(
 | 
			
		||||
                description="Android Authenticator with SafetyNet Attestation"
 | 
			
		||||
            )
 | 
			
		||||
            WebAuthnDeviceType.objects.filter(description="YubiKey 5 Series")
 | 
			
		||||
        )
 | 
			
		||||
        session = self.client.session
 | 
			
		||||
        plan = FlowPlan(flow_pk=flow.pk.hex)
 | 
			
		||||
@ -339,9 +337,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
 | 
			
		||||
            device_classes=[DeviceClasses.WEBAUTHN],
 | 
			
		||||
        )
 | 
			
		||||
        stage.webauthn_allowed_device_types.set(
 | 
			
		||||
            WebAuthnDeviceType.objects.filter(
 | 
			
		||||
                description="Android Authenticator with SafetyNet Attestation"
 | 
			
		||||
            )
 | 
			
		||||
            WebAuthnDeviceType.objects.filter(description="YubiKey 5 Series")
 | 
			
		||||
        )
 | 
			
		||||
        session = self.client.session
 | 
			
		||||
        plan = FlowPlan(flow_pk=flow.pk.hex)
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -141,9 +141,7 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
 | 
			
		||||
        """Test registration with restricted devices (fail)"""
 | 
			
		||||
        webauthn_mds_import.delay(force=True).get()
 | 
			
		||||
        self.stage.device_type_restrictions.set(
 | 
			
		||||
            WebAuthnDeviceType.objects.filter(
 | 
			
		||||
                description="Android Authenticator with SafetyNet Attestation"
 | 
			
		||||
            )
 | 
			
		||||
            WebAuthnDeviceType.objects.filter(description="YubiKey 5 Series")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@ from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
from django.utils.timezone import now
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from rest_framework.exceptions import ValidationError
 | 
			
		||||
from rest_framework.fields import CharField
 | 
			
		||||
 | 
			
		||||
from authentik.core.api.utils import PassiveSerializer
 | 
			
		||||
@ -47,6 +49,11 @@ class ConsentChallengeResponse(ChallengeResponse):
 | 
			
		||||
    component = CharField(default="ak-stage-consent")
 | 
			
		||||
    token = CharField(required=True)
 | 
			
		||||
 | 
			
		||||
    def validate_token(self, token: str):
 | 
			
		||||
        if token != self.stage.executor.request.session[SESSION_KEY_CONSENT_TOKEN]:
 | 
			
		||||
            raise ValidationError(_("Invalid consent token, re-showing prompt"))
 | 
			
		||||
        return token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConsentStageView(ChallengeStageView):
 | 
			
		||||
    """Simple consent checker."""
 | 
			
		||||
@ -120,9 +127,6 @@ class ConsentStageView(ChallengeStageView):
 | 
			
		||||
        return super().get(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
 | 
			
		||||
        if response.data["token"] != self.request.session[SESSION_KEY_CONSENT_TOKEN]:
 | 
			
		||||
            self.logger.info("Invalid consent token, re-showing prompt")
 | 
			
		||||
            return self.get(self.request)
 | 
			
		||||
        if self.should_always_prompt():
 | 
			
		||||
            return self.executor.stage_ok()
 | 
			
		||||
        current_stage: ConsentStage = self.executor.current_stage
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN
 | 
			
		||||
from authentik.lib.generators import generate_id
 | 
			
		||||
from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent
 | 
			
		||||
from authentik.stages.consent.stage import (
 | 
			
		||||
    PLAN_CONTEXT_CONSENT_HEADER,
 | 
			
		||||
    PLAN_CONTEXT_CONSENT_PERMISSIONS,
 | 
			
		||||
    SESSION_KEY_CONSENT_TOKEN,
 | 
			
		||||
)
 | 
			
		||||
@ -33,6 +34,40 @@ class TestConsentStage(FlowTestCase):
 | 
			
		||||
            slug=generate_id(),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_mismatched_token(self):
 | 
			
		||||
        """Test incorrect token"""
 | 
			
		||||
        flow = create_test_flow(FlowDesignation.AUTHENTICATION)
 | 
			
		||||
        stage = ConsentStage.objects.create(name=generate_id(), mode=ConsentMode.ALWAYS_REQUIRE)
 | 
			
		||||
        binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2)
 | 
			
		||||
 | 
			
		||||
        plan = FlowPlan(flow_pk=flow.pk.hex, bindings=[binding], markers=[StageMarker()])
 | 
			
		||||
        session = self.client.session
 | 
			
		||||
        session[SESSION_KEY_PLAN] = plan
 | 
			
		||||
        session.save()
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        session = self.client.session
 | 
			
		||||
        response = self.client.post(
 | 
			
		||||
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 | 
			
		||||
            {
 | 
			
		||||
                "token": generate_id(),
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertStageResponse(
 | 
			
		||||
            response,
 | 
			
		||||
            flow,
 | 
			
		||||
            component="ak-stage-consent",
 | 
			
		||||
            response_errors={
 | 
			
		||||
                "token": [{"string": "Invalid consent token, re-showing prompt", "code": "invalid"}]
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        self.assertFalse(UserConsent.objects.filter(user=self.user).exists())
 | 
			
		||||
 | 
			
		||||
    def test_always_required(self):
 | 
			
		||||
        """Test always required consent"""
 | 
			
		||||
        flow = create_test_flow(FlowDesignation.AUTHENTICATION)
 | 
			
		||||
@ -158,6 +193,7 @@ class TestConsentStage(FlowTestCase):
 | 
			
		||||
            context={
 | 
			
		||||
                PLAN_CONTEXT_APPLICATION: self.application,
 | 
			
		||||
                PLAN_CONTEXT_CONSENT_PERMISSIONS: [PermissionDict(id="foo", name="foo-desc")],
 | 
			
		||||
                PLAN_CONTEXT_CONSENT_HEADER: "test header",
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        session = self.client.session
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								authentik/stages/email/flow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								authentik/stages/email/flow.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
from base64 import b64encode
 | 
			
		||||
from copy import deepcopy
 | 
			
		||||
from pickle import dumps  # nosec
 | 
			
		||||
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
 | 
			
		||||
from authentik.flows.models import FlowToken, in_memory_stage
 | 
			
		||||
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, FlowPlan
 | 
			
		||||
from authentik.stages.consent.stage import PLAN_CONTEXT_CONSENT_HEADER, ConsentStageView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pickle_flow_token_for_email(plan: FlowPlan):
 | 
			
		||||
    """Insert a consent stage into the flow plan and pickle it for a FlowToken,
 | 
			
		||||
    to be sent via Email. This is to prevent automated email scanners, which sometimes
 | 
			
		||||
    open links in emails in a full browser from breaking the link."""
 | 
			
		||||
    plan_copy = deepcopy(plan)
 | 
			
		||||
    plan_copy.insert_stage(in_memory_stage(EmailTokenRevocationConsentStageView), index=0)
 | 
			
		||||
    plan_copy.context[PLAN_CONTEXT_CONSENT_HEADER] = _("Continue to confirm this email address.")
 | 
			
		||||
    data = dumps(plan_copy)
 | 
			
		||||
    return b64encode(data).decode()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmailTokenRevocationConsentStageView(ConsentStageView):
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        token: FlowToken = self.executor.plan.context[PLAN_CONTEXT_IS_RESTORED]
 | 
			
		||||
        try:
 | 
			
		||||
            token.refresh_from_db()
 | 
			
		||||
        except FlowToken.DoesNotExist:
 | 
			
		||||
            return self.executor.stage_invalid(
 | 
			
		||||
                _("Link was already used, please request a new link.")
 | 
			
		||||
            )
 | 
			
		||||
        return super().get(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def challenge_valid(self, response):
 | 
			
		||||
        token: FlowToken = self.executor.plan.context[PLAN_CONTEXT_IS_RESTORED]
 | 
			
		||||
        token.delete()
 | 
			
		||||
        return super().challenge_valid(response)
 | 
			
		||||
@ -23,6 +23,7 @@ from authentik.flows.stage import ChallengeStageView
 | 
			
		||||
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
 | 
			
		||||
from authentik.lib.utils.errors import exception_to_string
 | 
			
		||||
from authentik.lib.utils.time import timedelta_from_string
 | 
			
		||||
from authentik.stages.email.flow import pickle_flow_token_for_email
 | 
			
		||||
from authentik.stages.email.models import EmailStage
 | 
			
		||||
from authentik.stages.email.tasks import send_mails
 | 
			
		||||
from authentik.stages.email.utils import TemplateEmailMessage
 | 
			
		||||
@ -86,7 +87,8 @@ class EmailStageView(ChallengeStageView):
 | 
			
		||||
                user=pending_user,
 | 
			
		||||
                identifier=identifier,
 | 
			
		||||
                flow=self.executor.flow,
 | 
			
		||||
                _plan=FlowToken.pickle(self.executor.plan),
 | 
			
		||||
                _plan=pickle_flow_token_for_email(self.executor.plan),
 | 
			
		||||
                revoke_on_execution=False,
 | 
			
		||||
            )
 | 
			
		||||
        token = tokens.first()
 | 
			
		||||
        # Check if token is expired and rotate key if so
 | 
			
		||||
 | 
			
		||||
@ -174,5 +174,5 @@ class TestEmailStageSending(FlowTestCase):
 | 
			
		||||
                response = self.client.post(url)
 | 
			
		||||
            response = self.client.post(url)
 | 
			
		||||
            self.assertEqual(response.status_code, 200)
 | 
			
		||||
            self.assertTrue(len(mail.outbox) >= 1)
 | 
			
		||||
            self.assertGreaterEqual(len(mail.outbox), 1)
 | 
			
		||||
            self.assertEqual(mail.outbox[0].subject, "authentik")
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@ from authentik.flows.tests import FlowTestCase
 | 
			
		||||
from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_PLAN, FlowExecutorView
 | 
			
		||||
from authentik.lib.config import CONFIG
 | 
			
		||||
from authentik.lib.generators import generate_id
 | 
			
		||||
from authentik.stages.consent.stage import SESSION_KEY_CONSENT_TOKEN
 | 
			
		||||
from authentik.stages.email.models import EmailStage
 | 
			
		||||
from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE, EmailStageView
 | 
			
		||||
 | 
			
		||||
@ -160,6 +161,17 @@ class TestEmailStage(FlowTestCase):
 | 
			
		||||
                    kwargs={"flow_slug": self.flow.slug},
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            self.assertStageResponse(response, self.flow, component="ak-stage-consent")
 | 
			
		||||
            response = self.client.post(
 | 
			
		||||
                reverse(
 | 
			
		||||
                    "authentik_api:flow-executor",
 | 
			
		||||
                    kwargs={"flow_slug": self.flow.slug},
 | 
			
		||||
                ),
 | 
			
		||||
                data={
 | 
			
		||||
                    "token": self.client.session[SESSION_KEY_CONSENT_TOKEN],
 | 
			
		||||
                },
 | 
			
		||||
                follow=True,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            self.assertEqual(response.status_code, 200)
 | 
			
		||||
            self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
 | 
			
		||||
@ -182,6 +194,7 @@ class TestEmailStage(FlowTestCase):
 | 
			
		||||
        # Set flow token user to a different user
 | 
			
		||||
        token: FlowToken = FlowToken.objects.get(user=self.user)
 | 
			
		||||
        token.user = create_test_admin_user()
 | 
			
		||||
        token.revoke_on_execution = True
 | 
			
		||||
        token.save()
 | 
			
		||||
 | 
			
		||||
        with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ from rest_framework.fields import BooleanField, CharField
 | 
			
		||||
from authentik.core.models import Session, User
 | 
			
		||||
from authentik.events.middleware import audit_ignore
 | 
			
		||||
from authentik.flows.challenge import ChallengeResponse, WithUserInfoChallenge
 | 
			
		||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_SOURCE
 | 
			
		||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
 | 
			
		||||
from authentik.flows.stage import ChallengeStageView
 | 
			
		||||
from authentik.lib.utils.time import timedelta_from_string
 | 
			
		||||
from authentik.root.middleware import ClientIPMiddleware
 | 
			
		||||
@ -108,10 +108,6 @@ class UserLoginStageView(ChallengeStageView):
 | 
			
		||||
            flow_slug=self.executor.flow.slug,
 | 
			
		||||
            session_duration=delta,
 | 
			
		||||
        )
 | 
			
		||||
        # Only show success message if we don't have a source in the flow
 | 
			
		||||
        # as sources show their own success messages
 | 
			
		||||
        if not self.executor.plan.context.get(PLAN_CONTEXT_SOURCE, None):
 | 
			
		||||
            messages.success(self.request, _("Successfully logged in!"))
 | 
			
		||||
        if self.executor.current_stage.terminate_other_sessions:
 | 
			
		||||
            Session.objects.filter(
 | 
			
		||||
                authenticatedsession__user=user,
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
    "$schema": "http://json-schema.org/draft-07/schema",
 | 
			
		||||
    "$id": "https://goauthentik.io/blueprints/schema.json",
 | 
			
		||||
    "type": "object",
 | 
			
		||||
    "title": "authentik 2025.4.1 Blueprint schema",
 | 
			
		||||
    "title": "authentik 2025.6.0 Blueprint schema",
 | 
			
		||||
    "required": [
 | 
			
		||||
        "version",
 | 
			
		||||
        "entries"
 | 
			
		||||
@ -8147,6 +8147,12 @@
 | 
			
		||||
                    "title": "Group membership field",
 | 
			
		||||
                    "description": "Field which contains members of a group."
 | 
			
		||||
                },
 | 
			
		||||
                "user_membership_attribute": {
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "minLength": 1,
 | 
			
		||||
                    "title": "User membership attribute",
 | 
			
		||||
                    "description": "Attribute which matches the value of `group_membership_field`."
 | 
			
		||||
                },
 | 
			
		||||
                "object_uniqueness_field": {
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "minLength": 1,
 | 
			
		||||
@ -8180,6 +8186,11 @@
 | 
			
		||||
                    "type": "boolean",
 | 
			
		||||
                    "title": "Lookup groups from user",
 | 
			
		||||
                    "description": "Lookup group membership based on a user attribute instead of a group attribute. This allows nested group resolution on systems like FreeIPA and Active Directory"
 | 
			
		||||
                },
 | 
			
		||||
                "delete_not_found_objects": {
 | 
			
		||||
                    "type": "boolean",
 | 
			
		||||
                    "title": "Delete not found objects",
 | 
			
		||||
                    "description": "Delete authentik users and groups which were previously supplied by this source, but are now missing from it."
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "required": []
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ services:
 | 
			
		||||
    volumes:
 | 
			
		||||
      - redis:/data
 | 
			
		||||
  server:
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.0}
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    command: server
 | 
			
		||||
    environment:
 | 
			
		||||
@ -55,7 +55,7 @@ services:
 | 
			
		||||
      redis:
 | 
			
		||||
        condition: service_healthy
 | 
			
		||||
  worker:
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.0}
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    command: worker
 | 
			
		||||
    environment:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							@ -21,13 +21,13 @@ require (
 | 
			
		||||
	github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
 | 
			
		||||
	github.com/pires/go-proxyproto v0.8.1
 | 
			
		||||
	github.com/prometheus/client_golang v1.22.0
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.8.0
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.9.0
 | 
			
		||||
	github.com/sethvargo/go-envconfig v1.3.0
 | 
			
		||||
	github.com/sirupsen/logrus v1.9.3
 | 
			
		||||
	github.com/spf13/cobra v1.9.1
 | 
			
		||||
	github.com/stretchr/testify v1.10.0
 | 
			
		||||
	github.com/wwt/guac v1.3.2
 | 
			
		||||
	goauthentik.io/api/v3 v3.2025041.2
 | 
			
		||||
	goauthentik.io/api/v3 v3.2025041.4
 | 
			
		||||
	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
 | 
			
		||||
	golang.org/x/oauth2 v0.30.0
 | 
			
		||||
	golang.org/x/sync v0.14.0
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							@ -245,8 +245,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
 | 
			
		||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
 | 
			
		||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
 | 
			
		||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.9.0 h1:URbPQ4xVQSQhZ27WMQVmZSo3uT3pL+4IdHVcYq2nVfM=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
 | 
			
		||||
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=
 | 
			
		||||
@ -290,8 +290,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
 | 
			
		||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 | 
			
		||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 | 
			
		||||
goauthentik.io/api/v3 v3.2025041.2 h1:vFYYnhcDcxL95RczZwhzt3i4LptFXMvIRN+vgf8sQYg=
 | 
			
		||||
goauthentik.io/api/v3 v3.2025041.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
 | 
			
		||||
goauthentik.io/api/v3 v3.2025041.4 h1:cGqzWYnUHrWDoaXWDpIL/kWnX9sFrIhkYDye0P0OEAo=
 | 
			
		||||
goauthentik.io/api/v3 v3.2025041.4/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=
 | 
			
		||||
 | 
			
		||||
@ -33,4 +33,4 @@ func UserAgent() string {
 | 
			
		||||
	return fmt.Sprintf("authentik@%s", FullVersion())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const VERSION = "2025.4.1"
 | 
			
		||||
const VERSION = "2025.6.0"
 | 
			
		||||
 | 
			
		||||
@ -28,16 +28,18 @@ func NewSessionBinder(si server.LDAPServerInstance, oldBinder bind.Binder) *Sess
 | 
			
		||||
		si:  si,
 | 
			
		||||
		log: log.WithField("logger", "authentik.outpost.ldap.binder.session"),
 | 
			
		||||
	}
 | 
			
		||||
	if oldSb, ok := oldBinder.(*SessionBinder); ok {
 | 
			
		||||
		sb.DirectBinder = oldSb.DirectBinder
 | 
			
		||||
		sb.sessions = oldSb.sessions
 | 
			
		||||
		sb.log.Debug("re-initialised session binder")
 | 
			
		||||
	} else {
 | 
			
		||||
		sb.sessions = ttlcache.New(ttlcache.WithDisableTouchOnHit[Credentials, ldap.LDAPResultCode]())
 | 
			
		||||
		sb.DirectBinder = *direct.NewDirectBinder(si)
 | 
			
		||||
		go sb.sessions.Start()
 | 
			
		||||
		sb.log.Debug("initialised session binder")
 | 
			
		||||
	if oldBinder != nil {
 | 
			
		||||
		if oldSb, ok := oldBinder.(*SessionBinder); ok {
 | 
			
		||||
			sb.DirectBinder = oldSb.DirectBinder
 | 
			
		||||
			sb.sessions = oldSb.sessions
 | 
			
		||||
			sb.log.Debug("re-initialised session binder")
 | 
			
		||||
			return sb
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sb.sessions = ttlcache.New(ttlcache.WithDisableTouchOnHit[Credentials, ldap.LDAPResultCode]())
 | 
			
		||||
	sb.DirectBinder = *direct.NewDirectBinder(si)
 | 
			
		||||
	go sb.sessions.Start()
 | 
			
		||||
	sb.log.Debug("initialised session binder")
 | 
			
		||||
	return sb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import (
 | 
			
		||||
	memorybind "goauthentik.io/internal/outpost/ldap/bind/memory"
 | 
			
		||||
	"goauthentik.io/internal/outpost/ldap/constants"
 | 
			
		||||
	"goauthentik.io/internal/outpost/ldap/flags"
 | 
			
		||||
	"goauthentik.io/internal/outpost/ldap/search"
 | 
			
		||||
	directsearch "goauthentik.io/internal/outpost/ldap/search/direct"
 | 
			
		||||
	memorysearch "goauthentik.io/internal/outpost/ldap/search/memory"
 | 
			
		||||
)
 | 
			
		||||
@ -85,7 +86,11 @@ func (ls *LDAPServer) Refresh() error {
 | 
			
		||||
			providers[idx].certUUID = *kp
 | 
			
		||||
		}
 | 
			
		||||
		if *provider.SearchMode.Ptr() == api.LDAPAPIACCESSMODE_CACHED {
 | 
			
		||||
			providers[idx].searcher = memorysearch.NewMemorySearcher(providers[idx])
 | 
			
		||||
			var oldSearcher search.Searcher
 | 
			
		||||
			if existing != nil {
 | 
			
		||||
				oldSearcher = existing.searcher
 | 
			
		||||
			}
 | 
			
		||||
			providers[idx].searcher = memorysearch.NewMemorySearcher(providers[idx], oldSearcher)
 | 
			
		||||
		} else if *provider.SearchMode.Ptr() == api.LDAPAPIACCESSMODE_DIRECT {
 | 
			
		||||
			providers[idx].searcher = directsearch.NewDirectSearcher(providers[idx])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -31,13 +31,26 @@ type MemorySearcher struct {
 | 
			
		||||
	groups []api.Group
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
 | 
			
		||||
func NewMemorySearcher(si server.LDAPServerInstance, existing search.Searcher) *MemorySearcher {
 | 
			
		||||
	ms := &MemorySearcher{
 | 
			
		||||
		si:  si,
 | 
			
		||||
		log: log.WithField("logger", "authentik.outpost.ldap.searcher.memory"),
 | 
			
		||||
		ds:  direct.NewDirectSearcher(si),
 | 
			
		||||
	}
 | 
			
		||||
	if existing != nil {
 | 
			
		||||
		if ems, ok := existing.(*MemorySearcher); ok {
 | 
			
		||||
			ems.si = si
 | 
			
		||||
			ems.fetch()
 | 
			
		||||
			ems.log.Debug("re-initialised memory searcher")
 | 
			
		||||
			return ems
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ms.fetch()
 | 
			
		||||
	ms.log.Debug("initialised memory searcher")
 | 
			
		||||
	return ms
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ms *MemorySearcher) fetch() {
 | 
			
		||||
	// Error is not handled here, we get an empty/truncated list and the error is logged
 | 
			
		||||
	users, _ := ak.Paginator(ms.si.GetAPIClient().CoreApi.CoreUsersList(context.TODO()).IncludeGroups(true), ak.PaginatorOptions{
 | 
			
		||||
		PageSize: 100,
 | 
			
		||||
@ -49,7 +62,6 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
 | 
			
		||||
		Logger:   ms.log,
 | 
			
		||||
	})
 | 
			
		||||
	ms.groups = groups
 | 
			
		||||
	return ms
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ms *MemorySearcher) SearchBase(req *search.Request) (ldap.ServerSearchResult, error) {
 | 
			
		||||
 | 
			
		||||
@ -67,11 +67,15 @@ func (ws *WebServer) configureStatic() {
 | 
			
		||||
 | 
			
		||||
	// Media files, if backend is file
 | 
			
		||||
	if config.Get().Storage.Media.Backend == "file" {
 | 
			
		||||
		fsMedia := http.StripPrefix("/media", http.FileServer(http.Dir(config.Get().Storage.Media.File.Path)))
 | 
			
		||||
		indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/media/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
			w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
 | 
			
		||||
			fsMedia.ServeHTTP(w, r)
 | 
			
		||||
		})
 | 
			
		||||
		fsMedia := http.FileServer(http.Dir(config.Get().Storage.Media.File.Path))
 | 
			
		||||
		indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/media/").Handler(pathStripper(
 | 
			
		||||
			http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
				w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
 | 
			
		||||
				fsMedia.ServeHTTP(w, r)
 | 
			
		||||
			}),
 | 
			
		||||
			"media/",
 | 
			
		||||
			config.Get().Web.Path,
 | 
			
		||||
		))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/help/").Handler(pathStripper(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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.1016.1",
 | 
			
		||||
                "aws-cdk": "^2.1017.1",
 | 
			
		||||
                "cross-env": "^7.0.3"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -17,9 +17,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/aws-cdk": {
 | 
			
		||||
            "version": "2.1016.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1016.1.tgz",
 | 
			
		||||
            "integrity": "sha512-248TBiluT8jHUjkpzvWJOHv2fS+An9fiII3eji8H7jwfTu5yMBk7on4B/AVNr9A1GXJk9I32qf9Q0A3rLWRYPQ==",
 | 
			
		||||
            "version": "2.1017.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1017.1.tgz",
 | 
			
		||||
            "integrity": "sha512-KtDdkMhfVjDeexjpMrVoSlz2mTYI5BE/KotvJ7iFbZy1G0nkpW1ImZ54TdBefeeFmZ+8DAjU3I6nUFtymyOI1A==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "bin": {
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@
 | 
			
		||||
        "node": ">=20"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "aws-cdk": "^2.1016.1",
 | 
			
		||||
        "aws-cdk": "^2.1017.1",
 | 
			
		||||
        "cross-env": "^7.0.3"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ Parameters:
 | 
			
		||||
    Description: authentik Docker image
 | 
			
		||||
  AuthentikVersion:
 | 
			
		||||
    Type: String
 | 
			
		||||
    Default: 2025.4.1
 | 
			
		||||
    Default: 2025.6.0
 | 
			
		||||
    Description: authentik Docker image tag
 | 
			
		||||
  AuthentikServerCPU:
 | 
			
		||||
    Type: Number
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -32,15 +32,17 @@
 | 
			
		||||
# datenschmutz, 2025
 | 
			
		||||
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
 | 
			
		||||
# Dominic Wagner <mail@dominic-wagner.de>, 2025
 | 
			
		||||
# Till-Frederik Riechard, 2025
 | 
			
		||||
# Alexander Mnich, 2025
 | 
			
		||||
# 
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Dominic Wagner <mail@dominic-wagner.de>, 2025\n"
 | 
			
		||||
"Last-Translator: Alexander Mnich, 2025\n"
 | 
			
		||||
"Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n"
 | 
			
		||||
"MIME-Version: 1.0\n"
 | 
			
		||||
"Content-Type: text/plain; charset=UTF-8\n"
 | 
			
		||||
@ -132,6 +134,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Vom Authentik-Core-Webserver verwendetes Zertifikat."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Marke"
 | 
			
		||||
@ -405,7 +411,7 @@ msgstr "Eigenschaften"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "session data"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Sitzungsdaten"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Session"
 | 
			
		||||
@ -533,7 +539,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Number of passwords to check against."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Anzahl Passwörter, gegen die geprüft wird."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
@ -543,18 +549,20 @@ msgstr "Passwort nicht im Kontext festgelegt"
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "This password has been used previously. Please choose a different one."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Dieses Passwort wurde in Vergangenheit bereits verwendet. Bitte nutzen Sie "
 | 
			
		||||
"ein anderes."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Password Uniqueness Policy"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Passwort-Einzigartigkeits-Richtlinie"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Password Uniqueness Policies"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Passwort-Einzigartigkeits-Richtlinien"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "User Password History"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Nutzer-Passwort-Historie"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policy.py
 | 
			
		||||
msgid "Enterprise required to access this feature."
 | 
			
		||||
@ -693,6 +701,33 @@ msgstr "Endgeräte"
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "Verifiziere deinen Browser..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -988,7 +1023,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/flows/models.py
 | 
			
		||||
msgid "Evaluate policies when the Stage is presented to the user."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Richtlinien auswerten, wenn die Phase dem Benutzer angezeigt wird."
 | 
			
		||||
 | 
			
		||||
#: authentik/flows/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
@ -1043,9 +1078,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Starte komplette Provider Synchronisation."
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "Synchonisiere Benutzer Seite {page}"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -1593,11 +1631,11 @@ msgstr "ES256 (Asymmetrische Verschlüsselung)"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/oauth2/models.py
 | 
			
		||||
msgid "ES384 (Asymmetric Encryption)"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "ES384 (Asymmetrische Verschlüsselung)"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/oauth2/models.py
 | 
			
		||||
msgid "ES512 (Asymmetric Encryption)"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "ES5122 (Asymmetrische Verschlüsselung)"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/oauth2/models.py
 | 
			
		||||
msgid "Scope used by the client"
 | 
			
		||||
@ -2183,11 +2221,11 @@ msgstr "Standard"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/scim/models.py
 | 
			
		||||
msgid "AWS"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "AWS"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/scim/models.py
 | 
			
		||||
msgid "Slack"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Slack"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/scim/models.py
 | 
			
		||||
msgid "Base URL to SCIM requests, usually ends in /v2"
 | 
			
		||||
@ -2199,7 +2237,7 @@ msgstr "Authentifizierungstoken"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/scim/models.py
 | 
			
		||||
msgid "SCIM Compatibility Mode"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "SCIM Kompatibilitätsmodus"
 | 
			
		||||
 | 
			
		||||
#: authentik/providers/scim/models.py
 | 
			
		||||
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
 | 
			
		||||
@ -2231,7 +2269,7 @@ msgstr "Rollen"
 | 
			
		||||
 | 
			
		||||
#: authentik/rbac/models.py
 | 
			
		||||
msgid "Initial Permissions"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Initiale Berechtigungen"
 | 
			
		||||
 | 
			
		||||
#: authentik/rbac/models.py
 | 
			
		||||
msgid "System permission"
 | 
			
		||||
@ -2487,6 +2525,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP Quelle"
 | 
			
		||||
@ -2504,20 +2548,25 @@ msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "LDAP Quelle Eigenschafts-Zuordnungen"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr "Benutzer LDAP-Quellverbindung"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Benutzer LDAP-Quellverbindungen"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "Group LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "LDAP Gruppen Quellverbindung"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "Group LDAP Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "LDAP Gruppen Quellverbindungen"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/signals.py
 | 
			
		||||
msgid "Password does not match Active Directory Complexity."
 | 
			
		||||
@ -2530,7 +2579,7 @@ msgstr "Kein Token empfangen."
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/oauth/models.py
 | 
			
		||||
msgid "HTTP Basic Authentication"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "HTTP Basic Authentifizierung"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/oauth/models.py
 | 
			
		||||
msgid "Include the client ID and secret as request parameters"
 | 
			
		||||
@ -2896,6 +2945,11 @@ msgstr "SAML Gruppen Quellverbindung"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "SAML Gruppen Quellverbindungen"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "SCIM Quelle"
 | 
			
		||||
@ -2930,7 +2984,7 @@ msgstr "Duo Geräte"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/authenticator_email/models.py
 | 
			
		||||
msgid "Email OTP"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "E-Mail Einmalpasswort"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/authenticator_email/models.py
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
@ -2963,11 +3017,11 @@ msgstr "Beim Rendern der E-Mail-Vorlage ist ein Fehler aufgetreten"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/authenticator_email/models.py
 | 
			
		||||
msgid "Email Device"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "E-Mail Gerät"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/authenticator_email/models.py
 | 
			
		||||
msgid "Email Devices"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "E-Mail Geräte"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/authenticator_email/stage.py
 | 
			
		||||
#: authentik/stages/authenticator_sms/stage.py
 | 
			
		||||
@ -2977,7 +3031,7 @@ msgstr "Code stimmt nicht überein"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/authenticator_email/stage.py
 | 
			
		||||
msgid "Invalid email"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Ungültige E-Mail"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
 | 
			
		||||
#: authentik/stages/email/templates/email/password_reset.html
 | 
			
		||||
@ -3273,6 +3327,10 @@ msgstr "Zustimmung der Benutzer"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Zustimmungen der Benutzer"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Verweigerungsstufe"
 | 
			
		||||
@ -3289,6 +3347,14 @@ msgstr "Dummy Stufe"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Dummy Stufen"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Passwort zurücksetzen"
 | 
			
		||||
@ -3890,10 +3956,11 @@ msgstr ""
 | 
			
		||||
#: authentik/tenants/models.py
 | 
			
		||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Reputation kann nicht niedriger als dieser Wert sein. Null oder negativ."
 | 
			
		||||
 | 
			
		||||
#: authentik/tenants/models.py
 | 
			
		||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Reputation kann nicht höher als dieser Wert sein. Null oder positiv."
 | 
			
		||||
 | 
			
		||||
#: authentik/tenants/models.py
 | 
			
		||||
msgid "The option configures the footer links on the flow executor pages."
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-20 00:10+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-06-04 00:12+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"
 | 
			
		||||
@ -961,13 +961,16 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of groups"
 | 
			
		||||
msgid "Syncing page {page} of {object_type}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
@ -2223,6 +2226,10 @@ msgstr ""
 | 
			
		||||
msgid "Consider Objects matching this filter to be Users."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "Attribute which matches the value of `group_membership_field`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "Field which contains members of a group."
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2252,6 +2259,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2268,6 +2281,11 @@ msgstr ""
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2639,6 +2657,11 @@ msgstr ""
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2994,6 +3017,10 @@ msgstr ""
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -3010,6 +3037,14 @@ msgstr ""
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -3462,10 +3497,6 @@ msgstr ""
 | 
			
		||||
msgid "No Pending user to login."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/user_login/stage.py
 | 
			
		||||
msgid "Successfully logged in!"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/user_logout/models.py
 | 
			
		||||
msgid "User Logout Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
										
											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-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
 | 
			
		||||
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
 | 
			
		||||
@ -109,6 +109,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Certificado Web usado por el servidor web Core de authentik"
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Marca"
 | 
			
		||||
@ -671,6 +675,33 @@ msgstr "Dispositivos de Punto de Conexión"
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "Verificando tu navegador..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -1009,9 +1040,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Iniciando sincronización completa de proveedor"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "Sincronizando página {page} de usuarios"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -2452,6 +2486,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "Fuente de LDAP"
 | 
			
		||||
@ -2468,6 +2508,11 @@ msgstr "Asignación de Propiedades de Fuente de LDAP"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "Asignaciones de Propiedades de Fuente de LDAP"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2859,6 +2904,11 @@ msgstr "Conexión de Fuente de SAML de Grupo"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "Conexiones de Fuente de SAML de Grupo"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "Fuente de SCIM"
 | 
			
		||||
@ -3245,6 +3295,10 @@ msgstr "Consentimiento del usuario"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Consentimientos del usuario"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Etapa de denegación"
 | 
			
		||||
@ -3261,6 +3315,14 @@ msgstr "Escenario ficticio"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Etapas ficticias"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Restablecimiento de contraseña"
 | 
			
		||||
 | 
			
		||||
										
											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-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Ville Ranki, 2025\n"
 | 
			
		||||
"Language-Team: Finnish (https://app.transifex.com/authentik/teams/119923/fi/)\n"
 | 
			
		||||
@ -106,6 +106,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Web-sertifikaatti, jota authentik Core -verkkopalvelin käyttää."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Brändi"
 | 
			
		||||
@ -658,6 +662,33 @@ msgstr "Päätelaitteet"
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "Selaintasi varmennetaan..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -996,9 +1027,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Käynnistetään palveluntarjoajan täysi synkronisointi"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "Synkronoidaan käyttäjien sivua {page}"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -2429,6 +2463,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP-lähde"
 | 
			
		||||
@ -2445,6 +2485,11 @@ msgstr "LDAP-lähteen ominaisuuskytkentä"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "LDAP-lähteen ominaisuuskytkennät"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2837,6 +2882,11 @@ msgstr "Ryhmän SAML-lähteen yhteys"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "Ryhmän SAML-lähteen yhteydet"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "SCIM-lähde"
 | 
			
		||||
@ -3216,6 +3266,10 @@ msgstr "Käyttäjän hyväksyntä"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Käyttäjän hyväksynnät"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Kieltovaihe"
 | 
			
		||||
@ -3232,6 +3286,14 @@ msgstr "Valevaihe"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Valevaiheet"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Salasanan nollaus"
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-20 00:10+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+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"
 | 
			
		||||
@ -1056,9 +1056,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Démarrage d'une synchronisation complète du fournisseur"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "Synchronisation de la page {page} d'utilisateurs"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr "Synchronisation des utilisateurs"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr "Synchronisation des groupes"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -2508,6 +2511,14 @@ msgstr ""
 | 
			
		||||
"plutôt que sur un attribut de groupe. Cela permet la résolution des groupes "
 | 
			
		||||
"imbriqués sur des systèmes tels que FreeIPA et Active Directory."
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Supprimer les utilisateurs et les groupes authentik qui étaient auparavant "
 | 
			
		||||
"fournis par cette source, mais qui en sont maintenant absents."
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "Source LDAP"
 | 
			
		||||
@ -2524,6 +2535,13 @@ msgstr "Mappage de propriété source LDAP"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "Mappages de propriété source LDAP"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"ID unique utilisé pour vérifier si cet objet existe toujours dans le "
 | 
			
		||||
"répertoire."
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr "Connexion de l'utilisateur à la source LDAP"
 | 
			
		||||
@ -2918,6 +2936,11 @@ msgstr "Connexion du groupe à la source SAML"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "Connexions du groupe à la source SAML"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr "Continuer vers {source_name}"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "Source SCIM"
 | 
			
		||||
@ -3308,6 +3331,10 @@ msgstr "Consentement Utilisateur"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Consentements Utilisateur"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr "Jeton de consentement invalide, réaffichage de l'invite"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Étape de Refus"
 | 
			
		||||
@ -3324,6 +3351,14 @@ msgstr "Étape factice"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Étapes factices"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr "Continuer pour confirmer cette adresse courriel."
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr "Ce lien a déjà été utilisé, veuillez en demander un nouveau."
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Réinitialiser le Mot de Passe"
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -20,7 +20,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
 | 
			
		||||
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
 | 
			
		||||
@ -114,6 +114,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Certificato Web utilizzato dal server Web authentik Core."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Brand"
 | 
			
		||||
@ -672,6 +676,33 @@ msgstr "Dispositivi di Accesso"
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "Verifica del tuo browser..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -1018,9 +1049,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Avvio della sincronizzazione completa del provider"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "Sincronizzando pagina {page} degli utenti"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -2463,6 +2497,12 @@ msgstr ""
 | 
			
		||||
"attributo di gruppo. Questo consente la risoluzione di gruppi nidificati su "
 | 
			
		||||
"sistemi come FreeIPA e Active Directory."
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "Sorgente LDAP"
 | 
			
		||||
@ -2479,6 +2519,11 @@ msgstr "Mappatura delle proprietà sorgente LDAP"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "Mappature delle proprietà della sorgente LDAP"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr "Connessione Sorgente LDAP Utente"
 | 
			
		||||
@ -2872,6 +2917,11 @@ msgstr "Connessione sorgente SAML di gruppo"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "Connessioni sorgente SAML di gruppo"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "Sorgente SCIM"
 | 
			
		||||
@ -3269,6 +3319,10 @@ msgstr "Consenso utente"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Consensi utente"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Fase di negazione"
 | 
			
		||||
@ -3285,6 +3339,14 @@ msgstr "Fase fittizia"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Fasi fittizie"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Ripristino password"
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: NavyStack, 2023\n"
 | 
			
		||||
"Language-Team: Korean (https://app.transifex.com/authentik/teams/119923/ko/)\n"
 | 
			
		||||
@ -99,6 +99,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Authentik Core 웹서버에서 사용하는 웹 인증서."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -625,6 +629,33 @@ msgstr ""
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -946,8 +977,11 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
@ -2263,6 +2297,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP 소스"
 | 
			
		||||
@ -2279,6 +2319,11 @@ msgstr ""
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2657,6 +2702,11 @@ msgstr ""
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -3017,6 +3067,10 @@ msgstr "사용자 동의"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "사용자 동의"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "거부 스테이지"
 | 
			
		||||
@ -3033,6 +3087,14 @@ msgstr "더미 스테이지"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "더미 스테이지"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "비밀번호 초기화"
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -19,7 +19,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Dany Sluijk, 2025\n"
 | 
			
		||||
"Language-Team: Dutch (https://app.transifex.com/authentik/teams/119923/nl/)\n"
 | 
			
		||||
@ -113,6 +113,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Webcertificaat gebruikt door de authentik Core-webserver."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Merk"
 | 
			
		||||
@ -191,6 +195,7 @@ msgid "User's display name."
 | 
			
		||||
msgstr "Weergavenaam van de gebruiker."
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
 | 
			
		||||
#: authentik/rbac/models.py
 | 
			
		||||
msgid "User"
 | 
			
		||||
msgstr "Gebruiker"
 | 
			
		||||
 | 
			
		||||
@ -379,6 +384,18 @@ msgstr "Eigenschapskoppeling"
 | 
			
		||||
msgid "Property Mappings"
 | 
			
		||||
msgstr "Eigenschapskoppelingen"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "session data"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Session"
 | 
			
		||||
msgstr "Sessie"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Sessions"
 | 
			
		||||
msgstr "Sessies"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Authenticated Session"
 | 
			
		||||
msgstr "Geauthenticeerde Sessie"
 | 
			
		||||
@ -486,6 +503,38 @@ msgstr "Licentie Gebruik"
 | 
			
		||||
msgid "License Usage Records"
 | 
			
		||||
msgstr "Licentie Gebruik Records"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Veldsleutel om te controleren, veldsleutels gedefinieerd in Prompt-stadia "
 | 
			
		||||
"zijn beschikbaar."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Number of passwords to check against."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
msgid "Password not set in context"
 | 
			
		||||
msgstr "Wachtwoord niet ingesteld in context"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "This password has been used previously. Please choose a different one."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Password Uniqueness Policy"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Password Uniqueness Policies"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "User Password History"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policy.py
 | 
			
		||||
msgid "Enterprise required to access this feature."
 | 
			
		||||
msgstr "Enterprise benodigd voor toegang tot deze functie."
 | 
			
		||||
@ -622,6 +671,33 @@ msgstr ""
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "Uw browser wordt geverifieerd..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -963,8 +1039,11 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
@ -1265,12 +1344,6 @@ msgstr ""
 | 
			
		||||
msgid "Clear Policy's cache metrics"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Veldsleutel om te controleren, veldsleutels gedefinieerd in Prompt-stadia "
 | 
			
		||||
"zijn beschikbaar."
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
 | 
			
		||||
msgstr "Hoe vaak het wachtwoordhash op haveibeenpwned mag voorkomen"
 | 
			
		||||
@ -1282,10 +1355,6 @@ msgstr ""
 | 
			
		||||
"Als de zxcvbn-score gelijk is aan of lager is dan deze waarde, zal het "
 | 
			
		||||
"beleid falen."
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
msgid "Password not set in context"
 | 
			
		||||
msgstr "Wachtwoord niet ingesteld in context"
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
msgid "Invalid password."
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -1327,20 +1396,6 @@ msgstr "Reputatie Score"
 | 
			
		||||
msgid "Reputation Scores"
 | 
			
		||||
msgstr "Reputatie Scores"
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/templates/policies/buffer.html
 | 
			
		||||
msgid "Waiting for authentication..."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/templates/policies/buffer.html
 | 
			
		||||
msgid ""
 | 
			
		||||
"You're already authenticating in another tab. This page will refresh once "
 | 
			
		||||
"authentication is completed."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/templates/policies/buffer.html
 | 
			
		||||
msgid "Authenticate in this tab"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/templates/policies/denied.html
 | 
			
		||||
msgid "Permission denied"
 | 
			
		||||
msgstr "Toestemming geweigerd"
 | 
			
		||||
@ -2160,6 +2215,10 @@ msgstr ""
 | 
			
		||||
msgid "Roles"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/rbac/models.py
 | 
			
		||||
msgid "Initial Permissions"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/rbac/models.py
 | 
			
		||||
msgid "System permission"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2392,6 +2451,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP-bron"
 | 
			
		||||
@ -2408,6 +2473,27 @@ msgstr ""
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "Group LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "Group LDAP Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/signals.py
 | 
			
		||||
msgid "Password does not match Active Directory Complexity."
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2417,6 +2503,14 @@ msgstr ""
 | 
			
		||||
msgid "No token received."
 | 
			
		||||
msgstr "Geen token ontvangen."
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/oauth/models.py
 | 
			
		||||
msgid "HTTP Basic Authentication"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/oauth/models.py
 | 
			
		||||
msgid "Include the client ID and secret as request parameters"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/oauth/models.py
 | 
			
		||||
msgid "Request Token URL"
 | 
			
		||||
msgstr "URL voor aanvragen van token"
 | 
			
		||||
@ -2458,6 +2552,12 @@ msgstr ""
 | 
			
		||||
msgid "Additional Scopes"
 | 
			
		||||
msgstr "Aanvullende scopes"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/oauth/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"How to perform authentication during an authorization_code token request "
 | 
			
		||||
"flow"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/oauth/models.py
 | 
			
		||||
msgid "OAuth Source"
 | 
			
		||||
msgstr "OAuth-bron"
 | 
			
		||||
@ -2769,6 +2869,11 @@ msgstr ""
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -3142,6 +3247,10 @@ msgstr "Gebruikerstoestemming"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Gebruikersinstemmingen"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Weigerfase"
 | 
			
		||||
@ -3158,6 +3267,14 @@ msgstr "Dummystadium"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Dummystadia"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Wachtwoordherstel"
 | 
			
		||||
@ -3357,6 +3474,12 @@ msgstr ""
 | 
			
		||||
"Wanneer ingeschakeld, slaagt de stap en gaat verder wanneer ongeldige "
 | 
			
		||||
"gebruikersgegevens zijn ingevoerd."
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/identification/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
 | 
			
		||||
" to skip straight to entering their password."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/identification/models.py
 | 
			
		||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
 | 
			
		||||
msgstr "Optionele inschrijvingsflow, die onderaan de pagina is gekoppeld."
 | 
			
		||||
@ -3742,6 +3865,14 @@ msgstr ""
 | 
			
		||||
"Gebeurtenissen worden verwijderd na deze duur. (Indeling: "
 | 
			
		||||
"weken=3;dagen=2;uren=3;seconden=2)."
 | 
			
		||||
 | 
			
		||||
#: authentik/tenants/models.py
 | 
			
		||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/tenants/models.py
 | 
			
		||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/tenants/models.py
 | 
			
		||||
msgid "The option configures the footer links on the flow executor pages."
 | 
			
		||||
msgstr "De optie stelt de voettekst links in op de flow uitvoer pagina's."
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Hugo Bicho, 2025\n"
 | 
			
		||||
"Language-Team: Portuguese (https://app.transifex.com/authentik/teams/119923/pt/)\n"
 | 
			
		||||
@ -105,6 +105,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Certificado Web usado pelo servidor web authentik Core."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Marca"
 | 
			
		||||
@ -662,6 +666,33 @@ msgstr "Dispositivos do ponto de ligação"
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "A verificar o seu browser..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -1007,9 +1038,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Iniciando a sincronização completa com o provedor"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "A sincronizar a página {page} dos utilizadores"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -2456,6 +2490,12 @@ msgstr ""
 | 
			
		||||
" um atributo do grupo. Isto permite a resolução de grupos hierárquicos em "
 | 
			
		||||
"sistemas como o FreeIPA e Active Directory."
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "Fonte LDAP"
 | 
			
		||||
@ -2472,6 +2512,11 @@ msgstr "Mapeamento de propriedades de fonte LDAP"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "Mapeamentos de propriedades de fonte LDAP"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr "Ligação à fonte LDAP de Utilizador"
 | 
			
		||||
@ -2865,6 +2910,11 @@ msgstr "Ligação à fonte SAML de Grupo"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "Ligações à fonte SAML de Grupo"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "Fonte SCIM"
 | 
			
		||||
@ -3255,6 +3305,10 @@ msgstr "Consentimento do Utilizador"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Consentimentos do Utilizador"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Etapa de negação"
 | 
			
		||||
@ -3271,6 +3325,14 @@ msgstr "Etapa fictícia"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Etapas fictícias"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Redefinição de Palavra-Passe"
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -8,19 +8,19 @@
 | 
			
		||||
# Josenivaldo Benito Junior, 2023
 | 
			
		||||
# Caio Lima, 2023
 | 
			
		||||
# Hacklab, 2023
 | 
			
		||||
# Wagner Santos, 2024
 | 
			
		||||
# Rafael Mundel, 2024
 | 
			
		||||
# Anderson Silva Andrade <anderson.asa89@gmail.com>, 2025
 | 
			
		||||
# Gil Poiares-Oliveira, 2025
 | 
			
		||||
# Wagner Santos, 2025
 | 
			
		||||
# 
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Gil Poiares-Oliveira, 2025\n"
 | 
			
		||||
"Last-Translator: Wagner Santos, 2025\n"
 | 
			
		||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
 | 
			
		||||
"MIME-Version: 1.0\n"
 | 
			
		||||
"Content-Type: text/plain; charset=UTF-8\n"
 | 
			
		||||
@ -112,6 +112,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Certificado da Web usado pelo servidor da web authentik Core."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Brand"
 | 
			
		||||
@ -271,11 +275,11 @@ msgstr "Aplicativos"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Application Entitlement"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Autorização de aplicação"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Application Entitlements"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Autorizações de aplicação"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Use the source-specific identifier"
 | 
			
		||||
@ -379,15 +383,15 @@ msgstr "Mapeamentos de propriedades"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "session data"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "dados de sessão"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Session"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Sessão"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Sessions"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Sessões"
 | 
			
		||||
 | 
			
		||||
#: authentik/core/models.py
 | 
			
		||||
msgid "Authenticated Session"
 | 
			
		||||
@ -505,7 +509,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Number of passwords to check against."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Número de senhas para verificar."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
@ -514,19 +518,19 @@ msgstr "Senha não definida no contexto"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "This password has been used previously. Please choose a different one."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "A senha já foi utilizada antes. Por favor, escolha uma diferente."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Password Uniqueness Policy"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Política de exclusividade de senha"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "Password Uniqueness Policies"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Políticas de exclusividade de senha"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policies/unique_password/models.py
 | 
			
		||||
msgid "User Password History"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Histórico de senhas do usuário"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/policy.py
 | 
			
		||||
msgid "Enterprise required to access this feature."
 | 
			
		||||
@ -610,39 +614,39 @@ msgstr "Chave de Assinatura"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "Key used to sign the SSF Events."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Chave utilizada para assinar os eventos SSF."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "Shared Signals Framework Provider"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Provedor de Shared Signals Framework"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "Shared Signals Framework Providers"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Provedores de Shared Signals Framework"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "Add stream to SSF provider"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Adicionar stream ao fornecedor SSF"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "SSF Stream"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Stream SSF"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "SSF Streams"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Streams SSF"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "SSF Stream Event"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Evento de stream SSF"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/models.py
 | 
			
		||||
msgid "SSF Stream Events"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Eventos de stream SSF"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/providers/ssf/tasks.py
 | 
			
		||||
msgid "Failed to send request"
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Falha ao enviar requisição"
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
 | 
			
		||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
 | 
			
		||||
@ -664,6 +668,33 @@ msgstr ""
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -681,7 +712,7 @@ msgstr ""
 | 
			
		||||
#: authentik/events/api/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Successfully started task {name}."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Tarefa {name} iniciada com sucesso."
 | 
			
		||||
 | 
			
		||||
#: authentik/events/models.py
 | 
			
		||||
msgid "Event"
 | 
			
		||||
@ -713,12 +744,16 @@ msgid ""
 | 
			
		||||
"Customize the body of the request. Mapping should return data that is JSON-"
 | 
			
		||||
"serializable."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Personalize o corpo do pedido. O mapeamento deve retornar dados que sejam "
 | 
			
		||||
"serializáveis em JSON."
 | 
			
		||||
 | 
			
		||||
#: authentik/events/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure additional headers to be sent. Mapping should return a dictionary "
 | 
			
		||||
"of key-value pairs"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Configurar cabeçalhos adicionais a serem enviados. O mapeamento deve "
 | 
			
		||||
"retornar um dicionário de pares chave-valor"
 | 
			
		||||
 | 
			
		||||
#: authentik/events/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
@ -998,8 +1033,11 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
@ -1314,7 +1352,7 @@ msgstr ""
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Password exists on {count} online lists."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "A senha está presente em {count}  listas de senhas vulneráveis."
 | 
			
		||||
 | 
			
		||||
#: authentik/policies/password/models.py
 | 
			
		||||
msgid "Password is too weak."
 | 
			
		||||
@ -2396,6 +2434,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "Fonte LDAP"
 | 
			
		||||
@ -2412,6 +2456,11 @@ msgstr ""
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2802,6 +2851,11 @@ msgstr ""
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -3174,6 +3228,10 @@ msgstr "Consentimento do usuário"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Consentimentos do usuário"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Negar Estágio"
 | 
			
		||||
@ -3190,6 +3248,14 @@ msgstr "Palco fictício"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Fases fictícias"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Redefinição de senha"
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -18,7 +18,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Marc Schmitt, 2025\n"
 | 
			
		||||
"Language-Team: Russian (https://app.transifex.com/authentik/teams/119923/ru/)\n"
 | 
			
		||||
@ -111,6 +111,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Web Certificate используемый для authentik Core webserver."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Бренд"
 | 
			
		||||
@ -669,6 +673,33 @@ msgstr "Конечные устройства"
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "Проверка вашего браузера..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -1009,8 +1040,11 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Запуск полной синхронизации провайдера"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
@ -2430,6 +2464,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "Источник LDAP"
 | 
			
		||||
@ -2446,6 +2486,11 @@ msgstr "Сопоставление свойства LDAP источника"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "Сопоставление свойств LDAP источника"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2842,6 +2887,11 @@ msgstr "Групповое подключение к источнику SAML"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "Групповые подключения к источнику SAML"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "Источник SCIM"
 | 
			
		||||
@ -3219,6 +3269,10 @@ msgstr "Согласие пользователя"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Согласия пользователя"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Этап отказа"
 | 
			
		||||
@ -3235,6 +3289,14 @@ msgstr "Фиктивный этап"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Фиктивные этапы"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Сброс пароля"
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
 | 
			
		||||
"Language-Team: Turkish (https://app.transifex.com/authentik/teams/119923/tr/)\n"
 | 
			
		||||
@ -107,6 +107,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "Authentik Core web sunucusu tarafından kullanılan Web Sertifikası."
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "Marka"
 | 
			
		||||
@ -659,6 +663,33 @@ msgstr "Uç Nokta Cihazları"
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr "Tarayıcınız doğrulanıyor..."
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -1000,8 +1031,11 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "Tam sağlayıcı senkronizasyonunu başlatma"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
@ -2430,6 +2464,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP Kaynağı"
 | 
			
		||||
@ -2446,6 +2486,11 @@ msgstr "LDAP Kaynak Özellik Eşlemesi"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "LDAP Kaynak Özellik Eşlemeleri"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2837,6 +2882,11 @@ msgstr "Grup SAML Kaynak Bağlantısı"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "Grup SAML Kaynak Bağlantıları"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "SCIM Kaynak"
 | 
			
		||||
@ -3211,6 +3261,10 @@ msgstr "Kullanıcı Onayı"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "Kullanıcı Onayları"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "Aşama Alanını Reddet"
 | 
			
		||||
@ -3227,6 +3281,14 @@ msgstr "Kukla Aşaması"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "Kukla Aşamaları"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "Parola Sıfırlama"
 | 
			
		||||
 | 
			
		||||
										
											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-05-20 00:10+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+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"
 | 
			
		||||
@ -975,9 +975,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "开始全量提供程序同步"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "正在同步用户页面 {page}"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -2285,6 +2288,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr "基于用户属性而非组属性查询组成员身份。这允许在 FreeIPA 或 Active Directory 等系统上支持嵌套组决策"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP 源"
 | 
			
		||||
@ -2301,6 +2310,11 @@ msgstr "LDAP 源属性映射"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "LDAP 源属性映射"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr "用户 LDAP 源连接"
 | 
			
		||||
@ -2678,6 +2692,11 @@ msgstr "组 SAML 源连接"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "组 SAML 源连接"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "SCIM 源"
 | 
			
		||||
@ -3044,6 +3063,10 @@ msgstr "用户同意授权"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "用户同意授权"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "拒绝阶段"
 | 
			
		||||
@ -3060,6 +3083,14 @@ msgstr "虚拟阶段"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "虚拟阶段"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "密码重置"
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -14,7 +14,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-20 00:10+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+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"
 | 
			
		||||
@ -974,9 +974,12 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "开始全量提供程序同步"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgstr "正在同步用户页面 {page}"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr "正在同步用户"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr "正在同步组"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
@ -2284,6 +2287,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr "基于用户属性而非组属性查询组成员身份。这允许在 FreeIPA 或 Active Directory 等系统上支持嵌套组决策"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr "删除之前由此源提供,但现已缺失的用户和组。"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP 源"
 | 
			
		||||
@ -2300,6 +2309,11 @@ msgstr "LDAP 源属性映射"
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr "LDAP 源属性映射"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr "检查此对象是否仍在目录中时使用的唯一 ID。"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr "用户 LDAP 源连接"
 | 
			
		||||
@ -2677,6 +2691,11 @@ msgstr "组 SAML 源连接"
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr "组 SAML 源连接"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr "继续前往 {source_name}"
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "SCIM 源"
 | 
			
		||||
@ -3043,6 +3062,10 @@ msgstr "用户同意授权"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "用户同意授权"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr "无效的同意令牌,将重新显示输入"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "拒绝阶段"
 | 
			
		||||
@ -3059,6 +3082,14 @@ msgstr "虚拟阶段"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "虚拟阶段"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr "继续以确认电子邮件地址。"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr "链接已被使用,请申请一个新链接。"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "密码重置"
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
			
		||||
"Last-Translator: 刘松, 2025\n"
 | 
			
		||||
"Language-Team: Chinese (Taiwan) (https://app.transifex.com/authentik/teams/119923/zh_TW/)\n"
 | 
			
		||||
@ -101,6 +101,10 @@ msgstr ""
 | 
			
		||||
msgid "Web Certificate used by the authentik Core webserver."
 | 
			
		||||
msgstr "用於 authentik Core 網頁伺服器的網頁憑證。"
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Certificates used for client authentication."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/brands/models.py
 | 
			
		||||
msgid "Brand"
 | 
			
		||||
msgstr "品牌"
 | 
			
		||||
@ -625,6 +629,33 @@ msgstr ""
 | 
			
		||||
msgid "Verifying your browser..."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Configure certificate authorities to validate the certificate against. This "
 | 
			
		||||
"option has a higher priority than the `client_certificate` option on "
 | 
			
		||||
"`Brand`."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stage"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Mutual TLS Stages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/models.py
 | 
			
		||||
msgid "Permissions to pass Certificates for outposts."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "Certificate required but no certificate was given."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/mtls/stage.py
 | 
			
		||||
msgid "No user found for certificate."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/enterprise/stages/source/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Amount of time a user can take to return from the source to continue the "
 | 
			
		||||
@ -943,8 +974,11 @@ msgid "Starting full provider sync"
 | 
			
		||||
msgstr "開始同步所有提供程式"
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Syncing page {page} of users"
 | 
			
		||||
msgid "Syncing users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
msgid "Syncing groups"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/lib/sync/outgoing/tasks.py
 | 
			
		||||
@ -2249,6 +2283,12 @@ msgid ""
 | 
			
		||||
"Active Directory"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Delete authentik users and groups which were previously supplied by this "
 | 
			
		||||
"source, but are now missing from it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "LDAP Source"
 | 
			
		||||
msgstr "LDAP 來源"
 | 
			
		||||
@ -2265,6 +2305,11 @@ msgstr ""
 | 
			
		||||
msgid "LDAP Source Property Mappings"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid ""
 | 
			
		||||
"Unique ID used while checking if this object still exists in the directory."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/ldap/models.py
 | 
			
		||||
msgid "User LDAP Source Connection"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@ -2642,6 +2687,11 @@ msgstr ""
 | 
			
		||||
msgid "Group SAML Source Connections"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/saml/views.py
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Continue to {source_name}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/sources/scim/models.py
 | 
			
		||||
msgid "SCIM Source"
 | 
			
		||||
msgstr "SCIM 來源"
 | 
			
		||||
@ -2998,6 +3048,10 @@ msgstr "使用者同意"
 | 
			
		||||
msgid "User Consents"
 | 
			
		||||
msgstr "使用者同意"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/consent/stage.py
 | 
			
		||||
msgid "Invalid consent token, re-showing prompt"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/deny/models.py
 | 
			
		||||
msgid "Deny Stage"
 | 
			
		||||
msgstr "拒絕階段"
 | 
			
		||||
@ -3014,6 +3068,14 @@ msgstr "假階段"
 | 
			
		||||
msgid "Dummy Stages"
 | 
			
		||||
msgstr "假階段"
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Continue to confirm this email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/flow.py
 | 
			
		||||
msgid "Link was already used, please request a new link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: authentik/stages/email/models.py
 | 
			
		||||
msgid "Password Reset"
 | 
			
		||||
msgstr "重設密碼"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,12 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@goauthentik/authentik",
 | 
			
		||||
    "version": "2025.4.1",
 | 
			
		||||
    "version": "2025.6.0",
 | 
			
		||||
    "lockfileVersion": 3,
 | 
			
		||||
    "requires": true,
 | 
			
		||||
    "packages": {
 | 
			
		||||
        "": {
 | 
			
		||||
            "name": "@goauthentik/authentik",
 | 
			
		||||
            "version": "2025.4.1",
 | 
			
		||||
            "version": "2025.6.0",
 | 
			
		||||
            "devDependencies": {
 | 
			
		||||
                "@trivago/prettier-plugin-sort-imports": "^5.2.2",
 | 
			
		||||
                "prettier": "^3.3.3",
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@goauthentik/authentik",
 | 
			
		||||
    "version": "2025.4.1",
 | 
			
		||||
    "version": "2025.6.0",
 | 
			
		||||
    "private": true,
 | 
			
		||||
    "type": "module",
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										160
									
								
								packages/eslint-config/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										160
									
								
								packages/eslint-config/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -274,9 +274,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/js": {
 | 
			
		||||
            "version": "9.27.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
 | 
			
		||||
            "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
 | 
			
		||||
            "version": "9.28.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz",
 | 
			
		||||
            "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
@ -576,17 +576,17 @@
 | 
			
		||||
            "license": "MIT"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/eslint-plugin": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==",
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@eslint-community/regexpp": "^4.10.0",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/type-utils": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/utils": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/type-utils": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/utils": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.33.0",
 | 
			
		||||
                "graphemer": "^1.4.0",
 | 
			
		||||
                "ignore": "^7.0.0",
 | 
			
		||||
                "natural-compare": "^1.4.0",
 | 
			
		||||
@ -600,15 +600,15 @@
 | 
			
		||||
                "url": "https://opencollective.com/typescript-eslint"
 | 
			
		||||
            },
 | 
			
		||||
            "peerDependencies": {
 | 
			
		||||
                "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
 | 
			
		||||
                "@typescript-eslint/parser": "^8.33.0",
 | 
			
		||||
                "eslint": "^8.57.0 || ^9.0.0",
 | 
			
		||||
                "typescript": ">=4.8.4 <5.9.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
 | 
			
		||||
            "version": "7.0.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
 | 
			
		||||
            "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
 | 
			
		||||
            "version": "7.0.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
 | 
			
		||||
            "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -616,16 +616,16 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/parser": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==",
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/scope-manager": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/types": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/types": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.33.0",
 | 
			
		||||
                "debug": "^4.3.4"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -640,15 +640,16 @@
 | 
			
		||||
                "typescript": ">=4.8.4 <5.9.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/scope-manager": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==",
 | 
			
		||||
        "node_modules/@typescript-eslint/project-service": {
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/types": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.32.1"
 | 
			
		||||
                "@typescript-eslint/tsconfig-utils": "^8.33.0",
 | 
			
		||||
                "@typescript-eslint/types": "^8.33.0",
 | 
			
		||||
                "debug": "^4.3.4"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
@ -658,15 +659,50 @@
 | 
			
		||||
                "url": "https://opencollective.com/typescript-eslint"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/type-utils": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==",
 | 
			
		||||
        "node_modules/@typescript-eslint/scope-manager": {
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/utils": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/types": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.33.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": {
 | 
			
		||||
                "type": "opencollective",
 | 
			
		||||
                "url": "https://opencollective.com/typescript-eslint"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/tsconfig-utils": {
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": {
 | 
			
		||||
                "type": "opencollective",
 | 
			
		||||
                "url": "https://opencollective.com/typescript-eslint"
 | 
			
		||||
            },
 | 
			
		||||
            "peerDependencies": {
 | 
			
		||||
                "typescript": ">=4.8.4 <5.9.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/type-utils": {
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/utils": "8.33.0",
 | 
			
		||||
                "debug": "^4.3.4",
 | 
			
		||||
                "ts-api-utils": "^2.1.0"
 | 
			
		||||
            },
 | 
			
		||||
@ -683,9 +719,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/types": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==",
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -697,14 +733,16 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/typescript-estree": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==",
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/types": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/project-service": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/tsconfig-utils": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/types": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "8.33.0",
 | 
			
		||||
                "debug": "^4.3.4",
 | 
			
		||||
                "fast-glob": "^3.3.2",
 | 
			
		||||
                "is-glob": "^4.0.3",
 | 
			
		||||
@ -763,16 +801,16 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/utils": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==",
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@eslint-community/eslint-utils": "^4.7.0",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/types": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "8.32.1"
 | 
			
		||||
                "@typescript-eslint/scope-manager": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/types": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "8.33.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
@ -787,13 +825,13 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/visitor-keys": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==",
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/types": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/types": "8.33.0",
 | 
			
		||||
                "eslint-visitor-keys": "^4.2.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -1513,9 +1551,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/eslint": {
 | 
			
		||||
            "version": "9.27.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
 | 
			
		||||
            "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
 | 
			
		||||
            "version": "9.28.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
 | 
			
		||||
            "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@eslint-community/eslint-utils": "^4.2.0",
 | 
			
		||||
@ -1524,7 +1562,7 @@
 | 
			
		||||
                "@eslint/config-helpers": "^0.2.1",
 | 
			
		||||
                "@eslint/core": "^0.14.0",
 | 
			
		||||
                "@eslint/eslintrc": "^3.3.1",
 | 
			
		||||
                "@eslint/js": "9.27.0",
 | 
			
		||||
                "@eslint/js": "9.28.0",
 | 
			
		||||
                "@eslint/plugin-kit": "^0.3.1",
 | 
			
		||||
                "@humanfs/node": "^0.16.6",
 | 
			
		||||
                "@humanwhocodes/module-importer": "^1.0.1",
 | 
			
		||||
@ -3994,15 +4032,15 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/typescript-eslint": {
 | 
			
		||||
            "version": "8.32.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz",
 | 
			
		||||
            "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==",
 | 
			
		||||
            "version": "8.33.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz",
 | 
			
		||||
            "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/eslint-plugin": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/parser": "8.32.1",
 | 
			
		||||
                "@typescript-eslint/utils": "8.32.1"
 | 
			
		||||
                "@typescript-eslint/eslint-plugin": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/parser": "8.33.0",
 | 
			
		||||
                "@typescript-eslint/utils": "8.33.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,19 @@
 | 
			
		||||
[project]
 | 
			
		||||
name = "authentik"
 | 
			
		||||
version = "2025.4.1"
 | 
			
		||||
version = "2025.6.0"
 | 
			
		||||
description = ""
 | 
			
		||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
 | 
			
		||||
requires-python = "==3.13.*"
 | 
			
		||||
dependencies = [
 | 
			
		||||
    "argon2-cffi==23.1.0",
 | 
			
		||||
    "celery==5.5.2",
 | 
			
		||||
    "argon2-cffi==25.1.0",
 | 
			
		||||
    "celery==5.5.3",
 | 
			
		||||
    "channels==4.2.2",
 | 
			
		||||
    "channels-redis==4.2.1",
 | 
			
		||||
    "cryptography==45.0.3",
 | 
			
		||||
    "dacite==1.9.2",
 | 
			
		||||
    "deepmerge==2.0",
 | 
			
		||||
    "defusedxml==0.7.1",
 | 
			
		||||
    "django==5.2.1",
 | 
			
		||||
    "django==5.1.9",
 | 
			
		||||
    "django-countries==7.6.1",
 | 
			
		||||
    "django-cte==1.3.3",
 | 
			
		||||
    "django-filter==25.1",
 | 
			
		||||
@ -35,7 +35,7 @@ dependencies = [
 | 
			
		||||
    "flower==2.0.1",
 | 
			
		||||
    "geoip2==5.1.0",
 | 
			
		||||
    "geopy==2.4.1",
 | 
			
		||||
    "google-api-python-client==2.170.0",
 | 
			
		||||
    "google-api-python-client==2.171.0",
 | 
			
		||||
    "gssapi==1.9.0",
 | 
			
		||||
    "gunicorn==23.0.0",
 | 
			
		||||
    "jsonpatch==1.33",
 | 
			
		||||
@ -43,7 +43,7 @@ dependencies = [
 | 
			
		||||
    "kubernetes==32.0.1",
 | 
			
		||||
    "ldap3==2.9.1",
 | 
			
		||||
    "lxml==5.4.0",
 | 
			
		||||
    "msgraph-sdk==1.30.0",
 | 
			
		||||
    "msgraph-sdk==1.32.0",
 | 
			
		||||
    "opencontainers==0.0.14",
 | 
			
		||||
    "packaging==25.0",
 | 
			
		||||
    "paramiko==3.5.1",
 | 
			
		||||
@ -59,14 +59,14 @@ dependencies = [
 | 
			
		||||
    "sentry-sdk==2.29.1",
 | 
			
		||||
    "service-identity==24.2.0",
 | 
			
		||||
    "setproctitle==1.3.6",
 | 
			
		||||
    "structlog==25.3.0",
 | 
			
		||||
    "structlog==25.4.0",
 | 
			
		||||
    "swagger-spec-validator==3.0.4",
 | 
			
		||||
    "tenant-schemas-celery==4.0.1",
 | 
			
		||||
    "twilio==9.6.1",
 | 
			
		||||
    "twilio==9.6.2",
 | 
			
		||||
    "ua-parser==1.0.1",
 | 
			
		||||
    "unidecode==1.4.0",
 | 
			
		||||
    "urllib3<3",
 | 
			
		||||
    "uvicorn[standard]==0.34.2",
 | 
			
		||||
    "uvicorn[standard]==0.34.3",
 | 
			
		||||
    "watchdog==6.0.0",
 | 
			
		||||
    "webauthn==2.5.2",
 | 
			
		||||
    "wsproto==1.2.0",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								schema.yml
									
									
									
									
									
								
							@ -1,7 +1,7 @@
 | 
			
		||||
openapi: 3.0.3
 | 
			
		||||
info:
 | 
			
		||||
  title: authentik
 | 
			
		||||
  version: 2025.4.1
 | 
			
		||||
  version: 2025.6.0
 | 
			
		||||
  description: Making authentication simple.
 | 
			
		||||
  contact:
 | 
			
		||||
    email: hello@goauthentik.io
 | 
			
		||||
@ -28473,6 +28473,10 @@ paths:
 | 
			
		||||
        schema:
 | 
			
		||||
          type: string
 | 
			
		||||
          format: uuid
 | 
			
		||||
      - in: query
 | 
			
		||||
        name: delete_not_found_objects
 | 
			
		||||
        schema:
 | 
			
		||||
          type: boolean
 | 
			
		||||
      - in: query
 | 
			
		||||
        name: enabled
 | 
			
		||||
        schema:
 | 
			
		||||
@ -28577,6 +28581,10 @@ paths:
 | 
			
		||||
        name: sync_users_password
 | 
			
		||||
        schema:
 | 
			
		||||
          type: boolean
 | 
			
		||||
      - in: query
 | 
			
		||||
        name: user_membership_attribute
 | 
			
		||||
        schema:
 | 
			
		||||
          type: string
 | 
			
		||||
      - in: query
 | 
			
		||||
        name: user_object_filter
 | 
			
		||||
        schema:
 | 
			
		||||
@ -47889,6 +47897,9 @@ components:
 | 
			
		||||
        group_membership_field:
 | 
			
		||||
          type: string
 | 
			
		||||
          description: Field which contains members of a group.
 | 
			
		||||
        user_membership_attribute:
 | 
			
		||||
          type: string
 | 
			
		||||
          description: Attribute which matches the value of `group_membership_field`.
 | 
			
		||||
        object_uniqueness_field:
 | 
			
		||||
          type: string
 | 
			
		||||
          description: Field which contains a unique Identifier.
 | 
			
		||||
@ -47922,6 +47933,10 @@ components:
 | 
			
		||||
          description: Lookup group membership based on a user attribute instead of
 | 
			
		||||
            a group attribute. This allows nested group resolution on systems like
 | 
			
		||||
            FreeIPA and Active Directory
 | 
			
		||||
        delete_not_found_objects:
 | 
			
		||||
          type: boolean
 | 
			
		||||
          description: Delete authentik users and groups which were previously supplied
 | 
			
		||||
            by this source, but are now missing from it.
 | 
			
		||||
      required:
 | 
			
		||||
      - base_dn
 | 
			
		||||
      - component
 | 
			
		||||
@ -48098,6 +48113,10 @@ components:
 | 
			
		||||
          type: string
 | 
			
		||||
          minLength: 1
 | 
			
		||||
          description: Field which contains members of a group.
 | 
			
		||||
        user_membership_attribute:
 | 
			
		||||
          type: string
 | 
			
		||||
          minLength: 1
 | 
			
		||||
          description: Attribute which matches the value of `group_membership_field`.
 | 
			
		||||
        object_uniqueness_field:
 | 
			
		||||
          type: string
 | 
			
		||||
          minLength: 1
 | 
			
		||||
@ -48123,6 +48142,10 @@ components:
 | 
			
		||||
          description: Lookup group membership based on a user attribute instead of
 | 
			
		||||
            a group attribute. This allows nested group resolution on systems like
 | 
			
		||||
            FreeIPA and Active Directory
 | 
			
		||||
        delete_not_found_objects:
 | 
			
		||||
          type: boolean
 | 
			
		||||
          description: Delete authentik users and groups which were previously supplied
 | 
			
		||||
            by this source, but are now missing from it.
 | 
			
		||||
      required:
 | 
			
		||||
      - base_dn
 | 
			
		||||
      - name
 | 
			
		||||
@ -53431,6 +53454,10 @@ components:
 | 
			
		||||
          type: string
 | 
			
		||||
          minLength: 1
 | 
			
		||||
          description: Field which contains members of a group.
 | 
			
		||||
        user_membership_attribute:
 | 
			
		||||
          type: string
 | 
			
		||||
          minLength: 1
 | 
			
		||||
          description: Attribute which matches the value of `group_membership_field`.
 | 
			
		||||
        object_uniqueness_field:
 | 
			
		||||
          type: string
 | 
			
		||||
          minLength: 1
 | 
			
		||||
@ -53456,6 +53483,10 @@ components:
 | 
			
		||||
          description: Lookup group membership based on a user attribute instead of
 | 
			
		||||
            a group attribute. This allows nested group resolution on systems like
 | 
			
		||||
            FreeIPA and Active Directory
 | 
			
		||||
        delete_not_found_objects:
 | 
			
		||||
          type: boolean
 | 
			
		||||
          description: Delete authentik users and groups which were previously supplied
 | 
			
		||||
            by this source, but are now missing from it.
 | 
			
		||||
    PatchedLicenseRequest:
 | 
			
		||||
      type: object
 | 
			
		||||
      description: License Serializer
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								scripts/api-ts-templates/tsconfig.esm.mustache
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								scripts/api-ts-templates/tsconfig.esm.mustache
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "$schema": "https://json.schemastore.org/tsconfig",
 | 
			
		||||
    "extends": "./tsconfig.json",
 | 
			
		||||
    "compilerOptions": {
 | 
			
		||||
        "outDir": "dist/esm",
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								scripts/api-ts-templates/tsconfig.mustache
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								scripts/api-ts-templates/tsconfig.mustache
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
{
 | 
			
		||||
    "$schema": "https://json.schemastore.org/tsconfig",
 | 
			
		||||
    "compilerOptions": {
 | 
			
		||||
        "composite": true,
 | 
			
		||||
        "isolatedModules": true,
 | 
			
		||||
        "incremental": true,
 | 
			
		||||
        "baseUrl": ".",
 | 
			
		||||
        "rootDir": "src",
 | 
			
		||||
        "strict": true,
 | 
			
		||||
        "newLine": "lf",
 | 
			
		||||
        "target": "ESNext",
 | 
			
		||||
        "module": "ESNext",
 | 
			
		||||
        "moduleResolution": "bundler",
 | 
			
		||||
        "outDir": "dist",
 | 
			
		||||
        "skipDefaultLibCheck": true,
 | 
			
		||||
        "skipLibCheck": true,
 | 
			
		||||
        "sourceMap": true,
 | 
			
		||||
        "declaration": true,
 | 
			
		||||
        "declarationMap": true,
 | 
			
		||||
        "lib": ["DOM", "DOM.Iterable", "ESNext"],
 | 
			
		||||
    },
 | 
			
		||||
    "exclude": ["node_modules", "./out/**/*", "./dist/**/*"],
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
services:
 | 
			
		||||
  chrome:
 | 
			
		||||
    platform: linux/x86_64
 | 
			
		||||
    image: docker.io/selenium/standalone-chrome:136.0
 | 
			
		||||
    volumes:
 | 
			
		||||
      - /dev/shm:/dev/shm
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ from authentik.blueprints.tests import apply_blueprint
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.lib.config import CONFIG
 | 
			
		||||
from authentik.lib.generators import generate_id
 | 
			
		||||
from authentik.stages.identification.models import IdentificationStage
 | 
			
		||||
from tests.e2e.utils import SeleniumTestCase, retry
 | 
			
		||||
 | 
			
		||||
@ -17,6 +18,10 @@ from tests.e2e.utils import SeleniumTestCase, retry
 | 
			
		||||
class TestFlowsEnroll(SeleniumTestCase):
 | 
			
		||||
    """Test Enroll flow"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.username = generate_id()
 | 
			
		||||
 | 
			
		||||
    @retry()
 | 
			
		||||
    @apply_blueprint(
 | 
			
		||||
        "default/flow-default-authentication-flow.yaml",
 | 
			
		||||
@ -39,8 +44,8 @@ class TestFlowsEnroll(SeleniumTestCase):
 | 
			
		||||
        self.initial_stages()
 | 
			
		||||
        sleep(2)
 | 
			
		||||
 | 
			
		||||
        user = User.objects.get(username="foo")
 | 
			
		||||
        self.assertEqual(user.username, "foo")
 | 
			
		||||
        user = User.objects.get(username=self.username)
 | 
			
		||||
        self.assertEqual(user.username, self.username)
 | 
			
		||||
        self.assertEqual(user.name, "some name")
 | 
			
		||||
        self.assertEqual(user.email, "foo@bar.baz")
 | 
			
		||||
 | 
			
		||||
@ -87,7 +92,16 @@ class TestFlowsEnroll(SeleniumTestCase):
 | 
			
		||||
 | 
			
		||||
        sleep(2)
 | 
			
		||||
 | 
			
		||||
        self.assert_user(User.objects.get(username="foo"))
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor)
 | 
			
		||||
        consent_stage.find_element(
 | 
			
		||||
            By.CSS_SELECTOR,
 | 
			
		||||
            "[type=submit]",
 | 
			
		||||
        ).click()
 | 
			
		||||
 | 
			
		||||
        self.wait_for_url(self.if_user_url())
 | 
			
		||||
 | 
			
		||||
        self.assert_user(User.objects.get(username=self.username))
 | 
			
		||||
 | 
			
		||||
    def initial_stages(self):
 | 
			
		||||
        """Fill out initial stages"""
 | 
			
		||||
@ -105,7 +119,7 @@ class TestFlowsEnroll(SeleniumTestCase):
 | 
			
		||||
        wait = WebDriverWait(prompt_stage, self.wait_timeout)
 | 
			
		||||
 | 
			
		||||
        wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=username]")))
 | 
			
		||||
        prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").send_keys("foo")
 | 
			
		||||
        prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").send_keys(self.username)
 | 
			
		||||
        prompt_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
 | 
			
		||||
            self.user.username
 | 
			
		||||
        )
 | 
			
		||||
@ -124,3 +138,82 @@ class TestFlowsEnroll(SeleniumTestCase):
 | 
			
		||||
        prompt_stage.find_element(By.CSS_SELECTOR, "input[name=name]").send_keys("some name")
 | 
			
		||||
        prompt_stage.find_element(By.CSS_SELECTOR, "input[name=email]").send_keys("foo@bar.baz")
 | 
			
		||||
        prompt_stage.find_element(By.CSS_SELECTOR, ".pf-c-button").click()
 | 
			
		||||
 | 
			
		||||
    @retry()
 | 
			
		||||
    @apply_blueprint(
 | 
			
		||||
        "default/flow-default-authentication-flow.yaml",
 | 
			
		||||
        "default/flow-default-invalidation-flow.yaml",
 | 
			
		||||
    )
 | 
			
		||||
    @apply_blueprint(
 | 
			
		||||
        "example/flows-enrollment-email-verification.yaml",
 | 
			
		||||
    )
 | 
			
		||||
    @CONFIG.patch("email.port", 1025)
 | 
			
		||||
    def test_enroll_email_pretend_email_scanner(self):
 | 
			
		||||
        """Test enroll with Email verification. Open the email link twice to pretend we have an
 | 
			
		||||
        email scanner that clicks on links"""
 | 
			
		||||
        # Attach enrollment flow to identification stage
 | 
			
		||||
        ident_stage: IdentificationStage = IdentificationStage.objects.get(
 | 
			
		||||
            name="default-authentication-identification"
 | 
			
		||||
        )
 | 
			
		||||
        ident_stage.enrollment_flow = Flow.objects.get(slug="default-enrollment-flow")
 | 
			
		||||
        ident_stage.save()
 | 
			
		||||
 | 
			
		||||
        self.driver.get(self.live_server_url)
 | 
			
		||||
        self.initial_stages()
 | 
			
		||||
 | 
			
		||||
        # Email stage
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        email_stage = self.get_shadow_root("ak-stage-email", flow_executor)
 | 
			
		||||
 | 
			
		||||
        wait = WebDriverWait(email_stage, self.wait_timeout)
 | 
			
		||||
 | 
			
		||||
        # Wait for the success message so we know the email is sent
 | 
			
		||||
        wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, ".pf-c-form p")))
 | 
			
		||||
 | 
			
		||||
        # Open Mailpit
 | 
			
		||||
        self.driver.get("http://localhost:8025")
 | 
			
		||||
 | 
			
		||||
        # Click on first message
 | 
			
		||||
        self.wait.until(ec.presence_of_element_located((By.CLASS_NAME, "message")))
 | 
			
		||||
        self.driver.find_element(By.CLASS_NAME, "message").click()
 | 
			
		||||
        self.driver.switch_to.frame(self.driver.find_element(By.ID, "preview-html"))
 | 
			
		||||
        confirmation_link = self.driver.find_element(By.ID, "confirm").get_attribute("href")
 | 
			
		||||
 | 
			
		||||
        main_tab = self.driver.current_window_handle
 | 
			
		||||
 | 
			
		||||
        self.driver.switch_to.new_window("tab")
 | 
			
		||||
        confirm_tab = self.driver.current_window_handle
 | 
			
		||||
 | 
			
		||||
        # On the new tab, check that we have the confirmation screen
 | 
			
		||||
        self.driver.get(confirmation_link)
 | 
			
		||||
        self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-flow-executor")))
 | 
			
		||||
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            "Continue to confirm this email address.",
 | 
			
		||||
            consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Back on the main tab, confirm
 | 
			
		||||
        self.driver.switch_to.window(main_tab)
 | 
			
		||||
        self.driver.get(confirmation_link)
 | 
			
		||||
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor)
 | 
			
		||||
        consent_stage.find_element(
 | 
			
		||||
            By.CSS_SELECTOR,
 | 
			
		||||
            "[type=submit]",
 | 
			
		||||
        ).click()
 | 
			
		||||
 | 
			
		||||
        self.wait_for_url(self.if_user_url())
 | 
			
		||||
        sleep(2)
 | 
			
		||||
 | 
			
		||||
        self.assert_user(User.objects.get(username=self.username))
 | 
			
		||||
 | 
			
		||||
        self.driver.switch_to.window(confirm_tab)
 | 
			
		||||
        self.driver.refresh()
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        wait = WebDriverWait(flow_executor, self.wait_timeout)
 | 
			
		||||
        wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-stage-access-denied")))
 | 
			
		||||
 | 
			
		||||
@ -84,6 +84,14 @@ class TestFlowsRecovery(SeleniumTestCase):
 | 
			
		||||
        self.driver.switch_to.window(self.driver.window_handles[0])
 | 
			
		||||
 | 
			
		||||
        sleep(2)
 | 
			
		||||
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor)
 | 
			
		||||
        consent_stage.find_element(
 | 
			
		||||
            By.CSS_SELECTOR,
 | 
			
		||||
            "[type=submit]",
 | 
			
		||||
        ).click()
 | 
			
		||||
 | 
			
		||||
        # We can now enter the new password
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        prompt_stage = self.get_shadow_root("ak-stage-prompt", flow_executor)
 | 
			
		||||
 | 
			
		||||
@ -166,30 +166,35 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
 | 
			
		||||
            print("::group::authentik Logs", file=stderr)
 | 
			
		||||
        apps.get_app_config("authentik_tenants").ready()
 | 
			
		||||
        self.wait_timeout = 60
 | 
			
		||||
        self.logger = get_logger()
 | 
			
		||||
        self.driver = self._get_driver()
 | 
			
		||||
        self.driver.implicitly_wait(30)
 | 
			
		||||
        self.wait = WebDriverWait(self.driver, self.wait_timeout)
 | 
			
		||||
        self.logger = get_logger()
 | 
			
		||||
        self.user = create_test_admin_user()
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
    def _get_driver(self) -> WebDriver:
 | 
			
		||||
        count = 0
 | 
			
		||||
        try:
 | 
			
		||||
            opts = webdriver.ChromeOptions()
 | 
			
		||||
            opts.add_argument("--disable-search-engine-choice-screen")
 | 
			
		||||
            return webdriver.Chrome(options=opts)
 | 
			
		||||
        except WebDriverException:
 | 
			
		||||
            pass
 | 
			
		||||
        opts = webdriver.ChromeOptions()
 | 
			
		||||
        opts.add_argument("--disable-search-engine-choice-screen")
 | 
			
		||||
        # This breaks selenium when running remotely...?
 | 
			
		||||
        # opts.set_capability("goog:loggingPrefs", {"browser": "ALL"})
 | 
			
		||||
        opts.add_experimental_option(
 | 
			
		||||
            "prefs",
 | 
			
		||||
            {
 | 
			
		||||
                "profile.password_manager_leak_detection": False,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        while count < RETRIES:
 | 
			
		||||
            try:
 | 
			
		||||
                driver = webdriver.Remote(
 | 
			
		||||
                    command_executor="http://localhost:4444/wd/hub",
 | 
			
		||||
                    options=webdriver.ChromeOptions(),
 | 
			
		||||
                    options=opts,
 | 
			
		||||
                )
 | 
			
		||||
                driver.maximize_window()
 | 
			
		||||
                return driver
 | 
			
		||||
            except WebDriverException:
 | 
			
		||||
            except WebDriverException as exc:
 | 
			
		||||
                self.logger.warning("Failed to setup webdriver", exc=exc)
 | 
			
		||||
                count += 1
 | 
			
		||||
        raise ValueError(f"Webdriver failed after {RETRIES}.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										67
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										67
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							@ -104,14 +104,14 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "argon2-cffi"
 | 
			
		||||
version = "23.1.0"
 | 
			
		||||
version = "25.1.0"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "argon2-cffi-bindings" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798, upload-time = "2023-08-15T14:13:12.711Z" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124, upload-time = "2023-08-15T14:13:10.752Z" },
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -164,7 +164,7 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "authentik"
 | 
			
		||||
version = "2025.4.1"
 | 
			
		||||
version = "2025.6.0"
 | 
			
		||||
source = { editable = "." }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "argon2-cffi" },
 | 
			
		||||
@ -265,15 +265,15 @@ dev = [
 | 
			
		||||
 | 
			
		||||
[package.metadata]
 | 
			
		||||
requires-dist = [
 | 
			
		||||
    { name = "argon2-cffi", specifier = "==23.1.0" },
 | 
			
		||||
    { name = "celery", specifier = "==5.5.2" },
 | 
			
		||||
    { name = "argon2-cffi", specifier = "==25.1.0" },
 | 
			
		||||
    { name = "celery", specifier = "==5.5.3" },
 | 
			
		||||
    { name = "channels", specifier = "==4.2.2" },
 | 
			
		||||
    { name = "channels-redis", specifier = "==4.2.1" },
 | 
			
		||||
    { name = "cryptography", specifier = "==45.0.3" },
 | 
			
		||||
    { name = "dacite", specifier = "==1.9.2" },
 | 
			
		||||
    { name = "deepmerge", specifier = "==2.0" },
 | 
			
		||||
    { name = "defusedxml", specifier = "==0.7.1" },
 | 
			
		||||
    { name = "django", specifier = "==5.2.1" },
 | 
			
		||||
    { name = "django", specifier = "==5.1.9" },
 | 
			
		||||
    { name = "django-countries", specifier = "==7.6.1" },
 | 
			
		||||
    { name = "django-cte", specifier = "==1.3.3" },
 | 
			
		||||
    { name = "django-filter", specifier = "==25.1" },
 | 
			
		||||
@ -295,7 +295,7 @@ requires-dist = [
 | 
			
		||||
    { name = "flower", specifier = "==2.0.1" },
 | 
			
		||||
    { name = "geoip2", specifier = "==5.1.0" },
 | 
			
		||||
    { name = "geopy", specifier = "==2.4.1" },
 | 
			
		||||
    { name = "google-api-python-client", specifier = "==2.170.0" },
 | 
			
		||||
    { name = "google-api-python-client", specifier = "==2.171.0" },
 | 
			
		||||
    { name = "gssapi", specifier = "==1.9.0" },
 | 
			
		||||
    { name = "gunicorn", specifier = "==23.0.0" },
 | 
			
		||||
    { name = "jsonpatch", specifier = "==1.33" },
 | 
			
		||||
@ -303,7 +303,7 @@ requires-dist = [
 | 
			
		||||
    { name = "kubernetes", specifier = "==32.0.1" },
 | 
			
		||||
    { name = "ldap3", specifier = "==2.9.1" },
 | 
			
		||||
    { name = "lxml", specifier = "==5.4.0" },
 | 
			
		||||
    { name = "msgraph-sdk", specifier = "==1.30.0" },
 | 
			
		||||
    { name = "msgraph-sdk", specifier = "==1.32.0" },
 | 
			
		||||
    { name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
 | 
			
		||||
    { name = "packaging", specifier = "==25.0" },
 | 
			
		||||
    { name = "paramiko", specifier = "==3.5.1" },
 | 
			
		||||
@ -319,14 +319,14 @@ requires-dist = [
 | 
			
		||||
    { name = "sentry-sdk", specifier = "==2.29.1" },
 | 
			
		||||
    { name = "service-identity", specifier = "==24.2.0" },
 | 
			
		||||
    { name = "setproctitle", specifier = "==1.3.6" },
 | 
			
		||||
    { name = "structlog", specifier = "==25.3.0" },
 | 
			
		||||
    { name = "structlog", specifier = "==25.4.0" },
 | 
			
		||||
    { name = "swagger-spec-validator", specifier = "==3.0.4" },
 | 
			
		||||
    { name = "tenant-schemas-celery", specifier = "==4.0.1" },
 | 
			
		||||
    { name = "twilio", specifier = "==9.6.1" },
 | 
			
		||||
    { name = "twilio", specifier = "==9.6.2" },
 | 
			
		||||
    { name = "ua-parser", specifier = "==1.0.1" },
 | 
			
		||||
    { name = "unidecode", specifier = "==1.4.0" },
 | 
			
		||||
    { name = "urllib3", specifier = "<3" },
 | 
			
		||||
    { name = "uvicorn", extras = ["standard"], specifier = "==0.34.2" },
 | 
			
		||||
    { name = "uvicorn", extras = ["standard"], specifier = "==0.34.3" },
 | 
			
		||||
    { name = "watchdog", specifier = "==6.0.0" },
 | 
			
		||||
    { name = "webauthn", specifier = "==2.5.2" },
 | 
			
		||||
    { name = "wsproto", specifier = "==1.2.0" },
 | 
			
		||||
@ -645,7 +645,7 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "celery"
 | 
			
		||||
version = "5.5.2"
 | 
			
		||||
version = "5.5.3"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "billiard" },
 | 
			
		||||
@ -657,9 +657,9 @@ dependencies = [
 | 
			
		||||
    { name = "python-dateutil" },
 | 
			
		||||
    { name = "vine" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/03/5d9c6c449248958f1a5870e633a29d7419ff3724c452a98ffd22688a1a6a/celery-5.5.2.tar.gz", hash = "sha256:4d6930f354f9d29295425d7a37261245c74a32807c45d764bedc286afd0e724e", size = 1666892, upload-time = "2025-04-25T20:10:04.695Z" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/04/94/8e825ac1cf59d45d20c4345d4461e6b5263ae475f708d047c3dad0ac6401/celery-5.5.2-py3-none-any.whl", hash = "sha256:54425a067afdc88b57cd8d94ed4af2ffaf13ab8c7680041ac2c4ac44357bdf4c", size = 438626, upload-time = "2025-04-25T20:10:01.383Z" },
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -979,16 +979,16 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "django"
 | 
			
		||||
version = "5.2.1"
 | 
			
		||||
version = "5.1.9"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "asgiref" },
 | 
			
		||||
    { name = "sqlparse" },
 | 
			
		||||
    { name = "tzdata", marker = "sys_platform == 'win32'" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/10/0d546258772b8f31398e67c85e52c66ebc2b13a647193c3eef8ee433f1a8/django-5.2.1.tar.gz", hash = "sha256:57fe1f1b59462caed092c80b3dd324fd92161b620d59a9ba9181c34746c97284", size = 10818735, upload-time = "2025-05-07T14:06:17.543Z" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/10/08/2e6f05494b3fc0a3c53736846034f882b82ee6351791a7815bbb45715d79/django-5.1.9.tar.gz", hash = "sha256:565881bdd0eb67da36442e9ac788bda90275386b549070d70aee86327781a4fc", size = 10710887, upload-time = "2025-05-07T14:06:45.257Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/90/92/7448697b5838b3a1c6e1d2d6a673e908d0398e84dc4f803a2ce11e7ffc0f/django-5.2.1-py3-none-any.whl", hash = "sha256:a9b680e84f9a0e71da83e399f1e922e1ab37b2173ced046b541c72e1589a5961", size = 8301833, upload-time = "2025-05-07T14:06:10.955Z" },
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/e1/d1/d8b6b8250b84380d5a123e099ad3298a49407d81598faa13b43a2c6d96d7/django-5.1.9-py3-none-any.whl", hash = "sha256:2fd1d4a0a66a5ba702699eb692e75b0d828b73cc2f4e1fc4b6a854a918967411", size = 8277363, upload-time = "2025-05-07T14:06:37.426Z" },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -1397,7 +1397,7 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "google-api-python-client"
 | 
			
		||||
version = "2.170.0"
 | 
			
		||||
version = "2.171.0"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "google-api-core" },
 | 
			
		||||
@ -1406,10 +1406,7 @@ dependencies = [
 | 
			
		||||
    { name = "httplib2" },
 | 
			
		||||
    { name = "uritemplate" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/db/86/1bd09aea2664a46bc65713cb7876381ec8949a4b1e71be97dfc359c79781/google_api_python_client-2.170.0.tar.gz", hash = "sha256:75f3a1856f11418ea3723214e0abc59d9b217fd7ed43dcf743aab7f06ab9e2b1", size = 12971933, upload-time = "2025-05-22T20:39:52.802Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/ca/ab/928fb4551ce9c791de96b0681924d46de9a5b50931394fd19850383a08a1/google_api_python_client-2.170.0-py3-none-any.whl", hash = "sha256:7bf518a0527ad23322f070fa69f4f24053170d5c766821dc970ff0571ec22748", size = 13490660, upload-time = "2025-05-22T20:39:49.834Z" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/35/99/237cd2510aecca9fabb54007e58553274cc43cb3c18512ee1ea574d11b87/google_api_python_client-2.171.0.tar.gz", hash = "sha256:057a5c08d28463c6b9eb89746355de5f14b7ed27a65c11fdbf1d06c66bb66b23", size = 13028937, upload-time = "2025-06-03T18:57:38.732Z" }
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "google-auth"
 | 
			
		||||
@ -2066,7 +2063,7 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "msgraph-sdk"
 | 
			
		||||
version = "1.30.0"
 | 
			
		||||
version = "1.32.0"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "azure-identity" },
 | 
			
		||||
@ -2076,9 +2073,9 @@ dependencies = [
 | 
			
		||||
    { name = "microsoft-kiota-serialization-text" },
 | 
			
		||||
    { name = "msgraph-core" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/4a/4ff19671f6ea06f98fb2405f73a90350e4719ccc692e85e9e0c2fa066826/msgraph_sdk-1.30.0.tar.gz", hash = "sha256:59e30af6d7244c9009146d620c331e169701b651317746b16f561e2e2452e73f", size = 6608744, upload-time = "2025-05-13T13:09:12.594Z" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/2a/a5d67f631f8a0a9daa9059eed0f6703002efdf75f1d7575a289e23aceb5e/msgraph_sdk-1.32.0.tar.gz", hash = "sha256:485b25420ec4b3bf9c9d7abea166f9c51da99d9aad1bd57c191f84569af35143", size = 6628831, upload-time = "2025-06-03T18:22:21.526Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/70/95/451ec4db8a924274a1f7260809ea03fe9c2b446d84dc5238e92e49a1b522/msgraph_sdk-1.30.0-py3-none-any.whl", hash = "sha256:6748f5cdb5ddbcff9e4f3fb073dd0a604cb00e1cf285dd0fea6969c93ba8282f", size = 27140767, upload-time = "2025-05-13T13:09:07.718Z" },
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/b9/59/7e0f170611eb1a213940816703522ecca47ddffc149ef8e001e4789ddf13/msgraph_sdk-1.32.0-py3-none-any.whl", hash = "sha256:d1256f1669706c2703963ef0103470ce7a1b8a06f555506fc6b5f1069847877b", size = 27190465, upload-time = "2025-06-03T18:22:18.944Z" },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -3076,11 +3073,11 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "structlog"
 | 
			
		||||
version = "25.3.0"
 | 
			
		||||
version = "25.4.0"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/6a/b0b6d440e429d2267076c4819300d9929563b1da959cf1f68afbcd69fe45/structlog-25.3.0.tar.gz", hash = "sha256:8dab497e6f6ca962abad0c283c46744185e0c9ba900db52a423cb6db99f7abeb", size = 1367514, upload-time = "2025-04-25T16:00:39.167Z" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/79/b9/6e672db4fec07349e7a8a8172c1a6ae235c58679ca29c3f86a61b5e59ff3/structlog-25.4.0.tar.gz", hash = "sha256:186cd1b0a8ae762e29417095664adf1d6a31702160a46dacb7796ea82f7409e4", size = 1369138, upload-time = "2025-06-02T08:21:12.971Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/f5/52/7a2c7a317b254af857464da3d60a0d3730c44f912f8c510c76a738a207fd/structlog-25.3.0-py3-none-any.whl", hash = "sha256:a341f5524004c158498c3127eecded091eb67d3a611e7a3093deca30db06e172", size = 68240, upload-time = "2025-04-25T16:00:37.295Z" },
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/a0/4a/97ee6973e3a73c74c8120d59829c3861ea52210667ec3e7a16045c62b64d/structlog-25.4.0-py3-none-any.whl", hash = "sha256:fe809ff5c27e557d14e613f45ca441aabda051d119ee5a0102aaba6ce40eed2c", size = 68720, upload-time = "2025-06-02T08:21:11.43Z" },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -3161,7 +3158,7 @@ wheels = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "twilio"
 | 
			
		||||
version = "9.6.1"
 | 
			
		||||
version = "9.6.2"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "aiohttp" },
 | 
			
		||||
@ -3169,9 +3166,9 @@ dependencies = [
 | 
			
		||||
    { name = "pyjwt" },
 | 
			
		||||
    { name = "requests" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/95/78/453ff0d35442c53490c22d077f580684a2352846c721d3e01f4c6dfa85bd/twilio-9.6.1.tar.gz", hash = "sha256:bb80b31d4d9e55c33872efef7fb99373149ed4093f21c56cf582797da45862f5", size = 987002, upload-time = "2025-05-13T09:56:55.183Z" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/fa/c9/441a07f6552f2b504812501d56c41bd85b02afeef6c23ab8baf41ed6c70e/twilio-9.6.2.tar.gz", hash = "sha256:5da13bb497e39ece34cb9f2b3bc911f3288928612748f7688b3bda262c2767a1", size = 1041300, upload-time = "2025-05-29T12:25:04.59Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/02/f4/36fe2566a3ad7f71a89fd28ea2ebb6b2aa05c3a4d5a55b3ca6c358768c6b/twilio-9.6.1-py2.py3-none-any.whl", hash = "sha256:441fdab61b9a204eef770368380b962cbf08dc0fe9f757fe4b1d63ced37ddeed", size = 1859407, upload-time = "2025-05-13T09:56:53.094Z" },
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/67/91/382e83e5d205a7ae4325b66d40cd2fa6ce85526f2ed8fc553265e19abbe4/twilio-9.6.2-py2.py3-none-any.whl", hash = "sha256:8d4af6f42850734a921857df42940f7fed84e3e4a508d0d6bef5b9fb7dc08357", size = 1909253, upload-time = "2025-05-29T12:25:02.521Z" },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -3301,15 +3298,15 @@ socks = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "uvicorn"
 | 
			
		||||
version = "0.34.2"
 | 
			
		||||
version = "0.34.3"
 | 
			
		||||
source = { registry = "https://pypi.org/simple" }
 | 
			
		||||
dependencies = [
 | 
			
		||||
    { name = "click" },
 | 
			
		||||
    { name = "h11" },
 | 
			
		||||
]
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" }
 | 
			
		||||
sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" }
 | 
			
		||||
wheels = [
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" },
 | 
			
		||||
    { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[package.optional-dependencies]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1823
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1823
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -93,7 +93,7 @@
 | 
			
		||||
        "@floating-ui/dom": "^1.6.11",
 | 
			
		||||
        "@formatjs/intl-listformat": "^7.7.11",
 | 
			
		||||
        "@fortawesome/fontawesome-free": "^6.6.0",
 | 
			
		||||
        "@goauthentik/api": "^2025.4.1-1747687715",
 | 
			
		||||
        "@goauthentik/api": "^2025.4.1-1748622869",
 | 
			
		||||
        "@lit/context": "^1.1.2",
 | 
			
		||||
        "@lit/localize": "^0.12.2",
 | 
			
		||||
        "@lit/reactive-element": "^2.0.4",
 | 
			
		||||
@ -102,7 +102,7 @@
 | 
			
		||||
        "@open-wc/lit-helpers": "^0.7.0",
 | 
			
		||||
        "@patternfly/elements": "^4.1.0",
 | 
			
		||||
        "@patternfly/patternfly": "^4.224.2",
 | 
			
		||||
        "@sentry/browser": "^9.22.0",
 | 
			
		||||
        "@sentry/browser": "^9.24.0",
 | 
			
		||||
        "@spotlightjs/spotlight": "^2.13.3",
 | 
			
		||||
        "@webcomponents/webcomponentsjs": "^2.8.0",
 | 
			
		||||
        "base64-js": "^1.5.1",
 | 
			
		||||
@ -111,7 +111,7 @@
 | 
			
		||||
        "chartjs-adapter-date-fns": "^3.0.0",
 | 
			
		||||
        "codemirror": "^6.0.1",
 | 
			
		||||
        "construct-style-sheets-polyfill": "^3.1.0",
 | 
			
		||||
        "core-js": "^3.38.1",
 | 
			
		||||
        "core-js": "^3.42.0",
 | 
			
		||||
        "country-flag-icons": "^1.5.19",
 | 
			
		||||
        "date-fns": "^4.1.0",
 | 
			
		||||
        "deepmerge-ts": "^7.1.5",
 | 
			
		||||
@ -152,6 +152,7 @@
 | 
			
		||||
        "@storybook/addon-essentials": "^8.6.14",
 | 
			
		||||
        "@storybook/addon-links": "^8.6.14",
 | 
			
		||||
        "@storybook/blocks": "^8.6.12",
 | 
			
		||||
        "@storybook/channels": "^8.6.14",
 | 
			
		||||
        "@storybook/experimental-addon-test": "^8.6.14",
 | 
			
		||||
        "@storybook/manager-api": "^8.6.14",
 | 
			
		||||
        "@storybook/test": "^8.6.14",
 | 
			
		||||
@ -174,11 +175,11 @@
 | 
			
		||||
        "@wdio/spec-reporter": "^9.1.2",
 | 
			
		||||
        "@web/test-runner": "^0.20.2",
 | 
			
		||||
        "chromedriver": "^136.0.3",
 | 
			
		||||
        "esbuild": "^0.25.4",
 | 
			
		||||
        "esbuild": "^0.25.5",
 | 
			
		||||
        "esbuild-plugin-copy": "^2.1.1",
 | 
			
		||||
        "esbuild-plugin-polyfill-node": "^0.3.0",
 | 
			
		||||
        "esbuild-plugins-node-modules-polyfill": "^1.7.0",
 | 
			
		||||
        "eslint": "^9.11.1",
 | 
			
		||||
        "eslint": "^9.28.0",
 | 
			
		||||
        "eslint-plugin-lit": "^2.1.1",
 | 
			
		||||
        "eslint-plugin-wc": "^3.0.1",
 | 
			
		||||
        "github-slugger": "^2.0.0",
 | 
			
		||||
@ -193,7 +194,7 @@
 | 
			
		||||
        "storybook-addon-mock": "^5.0.0",
 | 
			
		||||
        "turnstile-types": "^1.2.3",
 | 
			
		||||
        "typescript": "^5.8.3",
 | 
			
		||||
        "typescript-eslint": "^8.32.1",
 | 
			
		||||
        "typescript-eslint": "^8.33.0",
 | 
			
		||||
        "vite-plugin-lit-css": "^2.0.0",
 | 
			
		||||
        "vite-tsconfig-paths": "^5.0.1",
 | 
			
		||||
        "wireit": "^0.14.12"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								web/packages/esbuild-plugin-live-reload/.github/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								web/packages/esbuild-plugin-live-reload/.github/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
_An ESBuild development plugin that watches for file changes and triggers automatic browser refreshes._
 | 
			
		||||
 | 
			
		||||
## Quick start
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
npm install -D @goauthentik/esbuild-plugin-live-reload
 | 
			
		||||
# Or with Yarn:
 | 
			
		||||
yarn add -D @goauthentik/esbuild-plugin-live-reload
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 1. Configure ESBuild
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import { liveReloadPlugin } from "@goauthentik/esbuild-plugin-live-reload";
 | 
			
		||||
import esbuild from "esbuild";
 | 
			
		||||
 | 
			
		||||
const NodeEnvironment = process.env.NODE_ENV || "development";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @type {esbuild.BuildOptions}
 | 
			
		||||
 */
 | 
			
		||||
const buildOptions = {
 | 
			
		||||
    // ... Your build options.
 | 
			
		||||
    define: {
 | 
			
		||||
        "process.env.NODE_ENV": JSON.stringify(NodeEnvironment),
 | 
			
		||||
    },
 | 
			
		||||
    plugins: [
 | 
			
		||||
        /** @see {@link LiveReloadPluginOptions} */
 | 
			
		||||
        liveReloadPlugin(),
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const buildContext = await esbuild.context(buildOptions);
 | 
			
		||||
 | 
			
		||||
await buildContext.rebuild();
 | 
			
		||||
await buildContext.watch();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 2. Connect your browser
 | 
			
		||||
 | 
			
		||||
Add the following import near the beginning of your application's entry point.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
if (process.env.NODE_ENV === "development") {
 | 
			
		||||
    await import("@goauthentik/esbuild-plugin-live-reload/client");
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
That's it! Your browser will now automatically refresh whenever ESBuild finishes rebuilding your code.
 | 
			
		||||
 | 
			
		||||
## About authentik
 | 
			
		||||
 | 
			
		||||
[authentik](https://goauthentik.io) is an open source Identity Provider that unifies your identity needs into a single platform, replacing Okta, Active Directory, and Auth0.
 | 
			
		||||
 | 
			
		||||
We built this plugin to streamline our development workflow, and we're sharing it with the community. If you have any questions, feature requests, or bug reports, please [open an issue](https://github.com/goauthentik/authentik/issues/new/choose).
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
This code is licensed under the [MIT License](https://www.tldrlegal.com/license/mit-license)
 | 
			
		||||
							
								
								
									
										3
									
								
								web/packages/esbuild-plugin-live-reload/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/packages/esbuild-plugin-live-reload/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
README.md
 | 
			
		||||
node_modules
 | 
			
		||||
_media
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user