Skip to content

Conversation

@dewi-tik
Copy link
Contributor

@dewi-tik dewi-tik commented Dec 5, 2025

Details

WIP - DRAFTY DRAFT

@netlify
Copy link

netlify bot commented Dec 5, 2025

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit eebfd45
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/69418fb9cfc68100083bfe1c
😎 Deploy Preview https://deploy-preview-18634--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@dewi-tik dewi-tik self-assigned this Dec 5, 2025
@netlify
Copy link

netlify bot commented Dec 5, 2025

👷 Deploy Preview for authentik-storybook processing.

Name Link
🔨 Latest commit eebfd45
🔍 Latest deploy log https://app.netlify.com/projects/authentik-storybook/deploys/69418fb99f0ee4000804b9b2

@dewi-tik dewi-tik moved this from Todo to In Progress in authentik Core Dec 5, 2025
@dewi-tik dewi-tik added this to the Release 2025.12 milestone Dec 5, 2025
@netlify
Copy link

netlify bot commented Dec 5, 2025

Deploy Preview for authentik-integrations ready!

Name Link
🔨 Latest commit eebfd45
🔍 Latest deploy log https://app.netlify.com/projects/authentik-integrations/deploys/69418fb968e5740008904d41
😎 Deploy Preview https://deploy-preview-18634--authentik-integrations.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov
Copy link

codecov bot commented Dec 5, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2862 1 2861 2
View the top 1 failed test(s) by shortest run time
tests.e2e.test_provider_oauth2_grafana.TestProviderOAuth2OAuth::test_authorization_logout
Stack Traces | 19.6s run time
self = <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>
sql = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None
ignored_wrapper_args = (False, {'connection': <DatabaseWrapper vendor='postgresql' alias='default'>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>})

    def _execute(self, sql, params, *ignored_wrapper_args):
        # Raise a warning during app initialization (stored_app_configs is only
        # ever set during testing).
        if not apps.ready and not apps.stored_app_configs:
            warnings.warn(self.APPS_NOT_READY_WARNING_MSG, category=RuntimeWarning)
        self.db.validate_no_broken_transaction()
        with self.db.wrap_database_errors:
            if params is None:
                # params default might be backend specific.
>               return self.cursor.execute(sql)

.venv/lib/python3.13.../db/backends/utils.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django_prometheus.db.common.ExportingCursorWrapper.<locals>.CursorWrapper [closed] [IDLE] (host=localhost user=authentik database=test_authentik) at 0x7fc877bf4350>
args = ('TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors...ice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";',)
kwargs = {}

    def execute(self, *args, **kwargs):
        execute_total.labels(alias, vendor).inc()
        with (
            query_duration_seconds.labels(**labels).time(),
            ExceptionCounterByType(errors_total, extra_labels=labels),
        ):
>           return super().execute(*args, **kwargs)

.venv/lib/python3.13.../django_prometheus/db/common.py:69: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django_prometheus.db.common.ExportingCursorWrapper.<locals>.CursorWrapper [closed] [IDLE] (host=localhost user=authentik database=test_authentik) at 0x7fc877bf4350>
query = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None

    def execute(
        self,
        query: Query,
        params: Params | None = None,
        *,
        prepare: bool | None = None,
        binary: bool | None = None,
    ) -> Self:
        """
        Execute a query or command to the database.
        """
        try:
            with self._conn.lock:
                self._conn.wait(
                    self._execute_gen(query, params, prepare=prepare, binary=binary)
                )
        except e._NO_TRACEBACK as ex:
>           raise ex.with_traceback(None)
E           psycopg.errors.DeadlockDetected: deadlock detected
E           DETAIL:  Process 298 waits for AccessExclusiveLock on relation 18411 of database 16389; blocked by process 321.
E           Process 321 waits for AccessShareLock on relation 19566 of database 16389; blocked by process 298.
E           HINT:  See server log for query details.

.venv/lib/python3.13....../site-packages/psycopg/cursor.py:97: DeadlockDetected

The above exception was the direct cause of the following exception:

self = <django.core.management.commands.flush.Command object at 0x7fc878aef890>
options = {'allow_cascade': False, 'database': 'default', 'force_color': False, 'inhibit_post_migrate': False, ...}
database = 'default'
connection = <DatabaseWrapper vendor='postgresql' alias='default'>
verbosity = 0, interactive = False, reset_sequences = False
allow_cascade = False, inhibit_post_migrate = False

        def handle(self, **options):
            database = options["database"]
            connection = connections[database]
            verbosity = options["verbosity"]
            interactive = options["interactive"]
            # The following are stealth options used by Django's internals.
            reset_sequences = options.get("reset_sequences", True)
            allow_cascade = options.get("allow_cascade", False)
            inhibit_post_migrate = options.get("inhibit_post_migrate", False)
    
            self.style = no_style()
    
            # Import the 'management' module within each installed app, to register
            # dispatcher events.
            for app_config in apps.get_app_configs():
                try:
                    import_module(".management", app_config.name)
                except ImportError:
                    pass
    
            sql_list = sql_flush(
                self.style,
                connection,
                reset_sequences=reset_sequences,
                allow_cascade=allow_cascade,
            )
    
            if interactive:
                confirm = input(
                    """You have requested a flush of the database.
    This will IRREVERSIBLY DESTROY all data currently in the "%s" database,
    and return each table to an empty state.
    Are you sure you want to do this?
    
        Type 'yes' to continue, or 'no' to cancel: """
                    % connection.settings_dict["NAME"]
                )
            else:
                confirm = "yes"
    
            if confirm == "yes":
                try:
>                   connection.ops.execute_sql_flush(sql_list)

.venv/lib/python3.13.../management/commands/flush.py:74: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psqlextra.backend.operations.PostgresOperations object at 0x7fc884a0c050>
sql_list = ['TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors...vice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";']

    def execute_sql_flush(self, sql_list):
        """Execute a list of SQL statements to flush the database."""
        with transaction.atomic(
            using=self.connection.alias,
            savepoint=self.connection.features.can_rollback_ddl,
        ):
            with self.connection.cursor() as cursor:
                for sql in sql_list:
>                   cursor.execute(sql)

.venv/lib/python3.13.../backends/base/operations.py:473: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>, 'TRUNCATE "authentik_providers_ssf_stream", "authe...vice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";')
kwargs = {}

    def runner(*args: "P.args", **kwargs: "P.kwargs"):
        # type: (...) -> R
        if sentry_sdk.get_client().get_integration(integration) is None:
            return original_function(*args, **kwargs)
    
>       return sentry_patched_function(*args, **kwargs)

.venv/lib/python3.13.../site-packages/sentry_sdk/utils.py:1816: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>
sql = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None

    @ensure_integration_enabled(DjangoIntegration, real_execute)
    def execute(self, sql, params=None):
        # type: (CursorWrapper, Any, Optional[Any]) -> Any
        with record_sql_queries(
            cursor=self.cursor,
            query=sql,
            params_list=params,
            paramstyle="format",
            executemany=False,
            span_origin=DjangoIntegration.origin_db,
        ) as span:
            _set_db_data(span, self)
>           result = real_execute(self, sql, params)

.venv/lib/python3.13.../integrations/django/__init__.py:651: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>
sql = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None

    def execute(self, sql, params=None):
>       return self._execute_with_wrappers(
            sql, params, many=False, executor=self._execute
        )

.venv/lib/python3.13.../db/backends/utils.py:79: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>
sql = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None, many = False
executor = <bound method CursorWrapper._execute of <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>>

    def _execute_with_wrappers(self, sql, params, many, executor):
        context = {"connection": self.db, "cursor": self}
        for wrapper in reversed(self.db.execute_wrappers):
            executor = functools.partial(wrapper, executor)
>       return executor(sql, params, many, context)

.venv/lib/python3.13.../db/backends/utils.py:92: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>
sql = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None
ignored_wrapper_args = (False, {'connection': <DatabaseWrapper vendor='postgresql' alias='default'>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>})

    def _execute(self, sql, params, *ignored_wrapper_args):
        # Raise a warning during app initialization (stored_app_configs is only
        # ever set during testing).
        if not apps.ready and not apps.stored_app_configs:
            warnings.warn(self.APPS_NOT_READY_WARNING_MSG, category=RuntimeWarning)
        self.db.validate_no_broken_transaction()
>       with self.db.wrap_database_errors:

.venv/lib/python3.13.../db/backends/utils.py:100: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.utils.DatabaseErrorWrapper object at 0x7fc87bf616a0>
exc_type = <class 'psycopg.errors.DeadlockDetected'>
exc_value = DeadlockDetected('deadlock detected\nDETAIL:  Process 298 waits for AccessExclusiveLock on relation 18411 of database ...ccessShareLock on relation 19566 of database 16389; blocked by process 298.\nHINT:  See server log for query details.')
traceback = <traceback object at 0x7fc8750bb380>

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return
        for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
        ):
            db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
            if issubclass(exc_type, db_exc_type):
                dj_exc_value = dj_exc_type(*exc_value.args)
                # Only set the 'errors_occurred' flag for errors that may make
                # the connection unusable.
                if dj_exc_type not in (DataError, IntegrityError):
                    self.wrapper.errors_occurred = True
>               raise dj_exc_value.with_traceback(traceback) from exc_value

.venv/lib/python3.13.../django/db/utils.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>
sql = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None
ignored_wrapper_args = (False, {'connection': <DatabaseWrapper vendor='postgresql' alias='default'>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7fc8751378f0>})

    def _execute(self, sql, params, *ignored_wrapper_args):
        # Raise a warning during app initialization (stored_app_configs is only
        # ever set during testing).
        if not apps.ready and not apps.stored_app_configs:
            warnings.warn(self.APPS_NOT_READY_WARNING_MSG, category=RuntimeWarning)
        self.db.validate_no_broken_transaction()
        with self.db.wrap_database_errors:
            if params is None:
                # params default might be backend specific.
>               return self.cursor.execute(sql)

.venv/lib/python3.13.../db/backends/utils.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django_prometheus.db.common.ExportingCursorWrapper.<locals>.CursorWrapper [closed] [IDLE] (host=localhost user=authentik database=test_authentik) at 0x7fc877bf4350>
args = ('TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors...ice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";',)
kwargs = {}

    def execute(self, *args, **kwargs):
        execute_total.labels(alias, vendor).inc()
        with (
            query_duration_seconds.labels(**labels).time(),
            ExceptionCounterByType(errors_total, extra_labels=labels),
        ):
>           return super().execute(*args, **kwargs)

.venv/lib/python3.13.../django_prometheus/db/common.py:69: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django_prometheus.db.common.ExportingCursorWrapper.<locals>.CursorWrapper [closed] [IDLE] (host=localhost user=authentik database=test_authentik) at 0x7fc877bf4350>
query = 'TRUNCATE "authentik_providers_ssf_stream", "authentik_endpoints_devicefactsnapshot", "authentik_endpoints_connectors_...evice", "authentik_providers_ldap_ldapprovider", "authentik_core_group", "authentik_providers_scim_scimprovidergroup";'
params = None

    def execute(
        self,
        query: Query,
        params: Params | None = None,
        *,
        prepare: bool | None = None,
        binary: bool | None = None,
    ) -> Self:
        """
        Execute a query or command to the database.
        """
        try:
            with self._conn.lock:
                self._conn.wait(
                    self._execute_gen(query, params, prepare=prepare, binary=binary)
                )
        except e._NO_TRACEBACK as ex:
>           raise ex.with_traceback(None)
E           django.db.utils.OperationalError: deadlock detected
E           DETAIL:  Process 298 waits for AccessExclusiveLock on relation 18411 of database 16389; blocked by process 321.
E           Process 321 waits for AccessShareLock on relation 19566 of database 16389; blocked by process 298.
E           HINT:  See server log for query details.

.venv/lib/python3.13....../site-packages/psycopg/cursor.py:97: OperationalError

The above exception was the direct cause of the following exception:

self = <tests.e2e.test_provider_oauth2_grafana.TestProviderOAuth2OAuth testMethod=test_authorization_logout>
result = <TestCaseFunction test_authorization_logout>, debug = False

    def _setup_and_call(self, result, debug=False):
        """
        Perform the following in order: pre-setup, run test, post-teardown,
        skipping pre/post hooks if test is set to be skipped.
    
        If debug=True, reraise any errors in setup and use super().debug()
        instead of __call__() to run the test.
        """
        testMethod = getattr(self, self._testMethodName)
        skipped = getattr(self.__class__, "__unittest_skip__", False) or getattr(
            testMethod, "__unittest_skip__", False
        )
    
        # Convert async test methods.
        if iscoroutinefunction(testMethod):
            setattr(self, self._testMethodName, async_to_sync(testMethod))
    
        if not skipped:
            try:
                if self.__class__._pre_setup_ran_eagerly:
                    self.__class__._pre_setup_ran_eagerly = False
                else:
                    self._pre_setup()
            except Exception:
                if debug:
                    raise
                result.addError(self, sys.exc_info())
                return
        if debug:
            super().debug()
        else:
            super().__call__(result)
        if not skipped:
            try:
>               self._post_teardown()

.venv/lib/python3.13.../django/test/testcases.py:379: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oauth2_grafana.TestProviderOAuth2OAuth testMethod=test_authorization_logout>

    def _post_teardown(self):
        """
        Perform post-test things:
        * Flush the contents of the database to leave a clean slate. If the
          class has an 'available_apps' attribute, don't fire post_migrate.
        * Force-close the connection so the next test gets a clean cursor.
        """
        try:
>           self._fixture_teardown()

.venv/lib/python3.13.../django/test/testcases.py:1231: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oauth2_grafana.TestProviderOAuth2OAuth testMethod=test_authorization_logout>

    def _fixture_teardown(self):
        # Allow TRUNCATE ... CASCADE and don't emit the post_migrate signal
        # when flushing only a subset of the apps
        for db_name in self._databases_names(include_mirrors=False):
            # Flush the database
            inhibit_post_migrate = (
                self.available_apps is not None
                or (  # Inhibit the post_migrate signal when using serialized
                    # rollback to avoid trying to recreate the serialized data.
                    self.serialized_rollback
                    and hasattr(connections[db_name], "_test_serialized_contents")
                )
            )
>           call_command(
                "flush",
                verbosity=0,
                interactive=False,
                database=db_name,
                reset_sequences=False,
                allow_cascade=self.available_apps is not None,
                inhibit_post_migrate=inhibit_post_migrate,
            )

.venv/lib/python3.13.../django/test/testcases.py:1266: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

command_name = 'flush', args = ()
options = {'allow_cascade': False, 'database': 'default', 'inhibit_post_migrate': False, 'interactive': False, ...}
command = <django.core.management.commands.flush.Command object at 0x7fc878aef890>
app_name = 'django.core'
parser = CommandParser(prog=' flush', usage=None, description='Removes ALL DATA from the database, including data added during ....', formatter_class=<class 'django.core.management.base.DjangoHelpFormatter'>, conflict_handler='error', add_help=True)
opt_mapping = {'database': 'database', 'force_color': 'force_color', 'help': 'help', 'no_color': 'no_color', ...}
arg_options = {'allow_cascade': False, 'database': 'default', 'inhibit_post_migrate': False, 'interactive': False, ...}
parse_args = []

    def call_command(command_name, *args, **options):
        """
        Call the given command, with the given options and args/kwargs.
    
        This is the primary API you should use for calling specific commands.
    
        `command_name` may be a string or a command object. Using a string is
        preferred unless the command object is required for further processing or
        testing.
    
        Some examples:
            call_command('migrate')
            call_command('shell', plain=True)
            call_command('sqlmigrate', 'myapp')
    
            from django.core.management.commands import flush
            cmd = flush.Command()
            call_command(cmd, verbosity=0, interactive=False)
            # Do something with cmd ...
        """
        if isinstance(command_name, BaseCommand):
            # Command object passed in.
            command = command_name
            command_name = command.__class__.__module__.split(".")[-1]
        else:
            # Load the command object by name.
            try:
                app_name = get_commands()[command_name]
            except KeyError:
                raise CommandError("Unknown command: %r" % command_name)
    
            if isinstance(app_name, BaseCommand):
                # If the command is already loaded, use it directly.
                command = app_name
            else:
                command = load_command_class(app_name, command_name)
    
        # Simulate argument parsing to get the option defaults (see #10080 for details).
        parser = command.create_parser("", command_name)
        # Use the `dest` option name from the parser option
        opt_mapping = {
            min(s_opt.option_strings).lstrip("-").replace("-", "_"): s_opt.dest
            for s_opt in parser._actions
            if s_opt.option_strings
        }
        arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
        parse_args = []
        for arg in args:
            if isinstance(arg, (list, tuple)):
                parse_args += map(str, arg)
            else:
                parse_args.append(str(arg))
    
        def get_actions(parser):
            # Parser actions and actions from sub-parser choices.
            for opt in parser._actions:
                if isinstance(opt, _SubParsersAction):
                    for sub_opt in opt.choices.values():
                        yield from get_actions(sub_opt)
                else:
                    yield opt
    
        parser_actions = list(get_actions(parser))
        mutually_exclusive_required_options = {
            opt
            for group in parser._mutually_exclusive_groups
            for opt in group._group_actions
            if group.required
        }
        # Any required arguments which are passed in via **options must be passed
        # to parse_args().
        for opt in parser_actions:
            if opt.dest in options and (
                opt.required or opt in mutually_exclusive_required_options
            ):
                opt_dest_count = sum(v == opt.dest for v in opt_mapping.values())
                if opt_dest_count > 1:
                    raise TypeError(
                        f"Cannot pass the dest {opt.dest!r} that matches multiple "
                        f"arguments via **options."
                    )
                parse_args.append(min(opt.option_strings))
                if isinstance(opt, (_AppendConstAction, _CountAction, _StoreConstAction)):
                    continue
                value = arg_options[opt.dest]
                if isinstance(value, (list, tuple)):
                    parse_args += map(str, value)
                else:
                    parse_args.append(str(value))
        defaults = parser.parse_args(args=parse_args)
        defaults = dict(defaults._get_kwargs(), **arg_options)
        # Raise an error if any unknown options were passed.
        stealth_options = set(command.base_stealth_options + command.stealth_options)
        dest_parameters = {action.dest for action in parser_actions}
        valid_options = (dest_parameters | stealth_options).union(opt_mapping)
        unknown_options = set(options) - valid_options
        if unknown_options:
            raise TypeError(
                "Unknown option(s) for %s command: %s. "
                "Valid options are: %s."
                % (
                    command_name,
                    ", ".join(sorted(unknown_options)),
                    ", ".join(sorted(valid_options)),
                )
            )
        # Move positional args out of options to mimic legacy optparse
        args = defaults.pop("args", ())
        if "skip_checks" not in options:
            defaults["skip_checks"] = True
    
>       return command.execute(*args, **defaults)

.venv/lib/python3.13.../core/management/__init__.py:194: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.core.management.commands.flush.Command object at 0x7fc878aef890>
args = ()
options = {'allow_cascade': False, 'database': 'default', 'force_color': False, 'inhibit_post_migrate': False, ...}

    def execute(self, *args, **options):
        """
        Try to execute this command, performing system checks if needed (as
        controlled by the ``requires_system_checks`` attribute, except if
        force-skipped).
        """
        if options["force_color"] and options["no_color"]:
            raise CommandError(
                "The --no-color and --force-color options can't be used together."
            )
        if options["force_color"]:
            self.style = color_style(force_color=True)
        elif options["no_color"]:
            self.style = no_style()
            self.stderr.style_func = None
        if options.get("stdout"):
            self.stdout = OutputWrapper(options["stdout"])
        if options.get("stderr"):
            self.stderr = OutputWrapper(options["stderr"])
    
        if self.requires_system_checks and not options["skip_checks"]:
            check_kwargs = self.get_check_kwargs(options)
            self.check(**check_kwargs)
        if self.requires_migrations_checks:
            self.check_migrations()
>       output = self.handle(*args, **options)

.venv/lib/python3.13.../core/management/base.py:460: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.core.management.commands.flush.Command object at 0x7fc878aef890>
options = {'allow_cascade': False, 'database': 'default', 'force_color': False, 'inhibit_post_migrate': False, ...}
database = 'default'
connection = <DatabaseWrapper vendor='postgresql' alias='default'>
verbosity = 0, interactive = False, reset_sequences = False
allow_cascade = False, inhibit_post_migrate = False

        def handle(self, **options):
            database = options["database"]
            connection = connections[database]
            verbosity = options["verbosity"]
            interactive = options["interactive"]
            # The following are stealth options used by Django's internals.
            reset_sequences = options.get("reset_sequences", True)
            allow_cascade = options.get("allow_cascade", False)
            inhibit_post_migrate = options.get("inhibit_post_migrate", False)
    
            self.style = no_style()
    
            # Import the 'management' module within each installed app, to register
            # dispatcher events.
            for app_config in apps.get_app_configs():
                try:
                    import_module(".management", app_config.name)
                except ImportError:
                    pass
    
            sql_list = sql_flush(
                self.style,
                connection,
                reset_sequences=reset_sequences,
                allow_cascade=allow_cascade,
            )
    
            if interactive:
                confirm = input(
                    """You have requested a flush of the database.
    This will IRREVERSIBLY DESTROY all data currently in the "%s" database,
    and return each table to an empty state.
    Are you sure you want to do this?
    
        Type 'yes' to continue, or 'no' to cancel: """
                    % connection.settings_dict["NAME"]
                )
            else:
                confirm = "yes"
    
            if confirm == "yes":
                try:
                    connection.ops.execute_sql_flush(sql_list)
                except Exception as exc:
>                   raise CommandError(
                        "Database %s couldn't be flushed. Possible reasons:\n"
                        "  * The database isn't running or isn't configured correctly.\n"
                        "  * At least one of the expected database tables doesn't exist.\n"
                        "  * The SQL was invalid.\n"
                        "Hint: Look at the output of 'django-admin sqlflush'. "
                        "That's the SQL this command wasn't able to run."
                        % (connection.settings_dict["NAME"],)
                    ) from exc
E                   django.core.management.base.CommandError: Database test_authentik couldn't be flushed. Possible reasons:
E                     * The database isn't running or isn't configured correctly.
E                     * At least one of the expected database tables doesn't exist.
E                     * The SQL was invalid.
E                   Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.

.venv/lib/python3.13.../management/commands/flush.py:76: CommandError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 11, 2025

authentik PR Installation instructions

Instructions for docker-compose

Add the following block to your .env file:

AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=gh-6c5ec1fafe06b7b25fc2435b148a870cfc8fcf3e
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s

Afterwards, run the upgrade commands from the latest release notes.

Instructions for Kubernetes

Add the following block to your values.yml file:

authentik:
    outposts:
        container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
    image:
        repository: ghcr.io/goauthentik/dev-server
        tag: gh-6c5ec1fafe06b7b25fc2435b148a870cfc8fcf3e

Afterwards, run the upgrade commands from the latest release notes.


import DocCardList from "@theme/DocCardList";

Device compliance lets authentik verify that a user's device meets security and configuration criteria, such as operating system version, disk encryption, antivirus status etc, before allowing access to resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with fleet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With or without fleet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so should be mentioned or made more clear imo

dewi-tik and others added 4 commits December 14, 2025 09:22

The Endpoint stage fetches [device facts](../../../../endpoint-devices/device-compliance/device-reporting.md#device-facts) via a configured [connector](../../../../endpoint-devices/device-compliance/connectors.md) for use in the flow. These device facts can be used by other stages and policies to make device compliance decisions.

### Connector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, this page has no section why not just make it H2s?


Select the [connector](../../../../endpoint-devices/device-compliance/connectors.md) that the Endpoint stage should use to obtain device information.

### Mode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here


### Mode

Select whether an endpoint device is required for the stage to succeed or not.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Select whether an endpoint device is required for the stage to succeed or not.
Select whether an endpoint device is required for the stage to succeed.

ak-sysd domains join <name_for_authentik_domain> -a <authentik_FQDN>
```

- `name_for_authentik_domain` is the name that will be used to identify the authentik deployment on the device.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as other doc

```

- `name_for_authentik_domain` is the name that will be used to identify the authentik deployment on the device.
- `authentik_FQDN` is the fully qualified domain name of the authentik deployment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as other doc

Where `URL` is the FQDN of your authentik deployment and `ClientID` is the Client ID of the [`authentik-cli` provider](../configuration.md#create-an-application-and-provider-in-authentik-for-cli).

3. Save the file as `authentik.reg` and ensure that **Save as type** is set to **All Files**.
4. Locale the `authentik.reg` file in File Explorer, right-click it and select **Merge**.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
4. Locale the `authentik.reg` file in File Explorer, right-click it and select **Merge**.
4. Locate the `authentik.reg` file in File Explorer, right-click it and select **Merge**.


You'll need to add a registry entry for WCP to work:

1. On the Windows device, open the Notepad application
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. On the Windows device, open the Notepad application
1. On the Windows device, open the Notepad application.


The authentik Agent primarily outputs logs to Windows Event Viewer.

WCP logs to the `wcp.log` file in `C:\Program Files\Authentik Security Inc\wcp`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WCP logs to the `wcp.log` file in `C:\Program Files\Authentik Security Inc\wcp`.
WCP logs to the `wcp.log` located in `C:\Program Files\Authentik Security Inc\wcp`.

Copy link
Member

@dominic-r dominic-r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

14/31 files reviewed. time for lunch


Establish an SSH connection with the target endpoint device.

```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```
```bash

tags: [authentik Agent, connector, configure, configuration]
---

To support the deployment of the authentik Agent on devices, you first need to configure your authentik deployment. This involves importing the [Device code flow](../../add-secure-apps/providers/oauth2/device_code.md), creating an OAuth application/provider pair, and creating a [Connector](../device-compliance/connectors.md).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To deploy the authentik agent, you must:

  • import rhe oauth device code flow
  • create an oauth application and provider for cli authentication
  • create an authentik agent connector

^^ like this cause the block feels quite convoluted otherwise


## Configure SSH authentication on an endpoint device

If you want a Linux Endpoint Device to support accepting SSH connections using authentik credentials, you will need to install the `libpam-authentik` package in addition the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you want a Linux Endpoint Device to support accepting SSH connections using authentik credentials, you will need to install the `libpam-authentik` package in addition the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.
If you want a Linux Endpoint Device to support accepting SSH connections using authentik credentials, you will need to install the `libpam-authentik` package in addition to the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.


## Configure sudo authorization on an endpoint device

If you want a Linux Endpoint Device to support authorizing using authentik credentials, you will need to install the `libpam-authentik` package in addition the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you want a Linux Endpoint Device to support authorizing using authentik credentials, you will need to install the `libpam-authentik` package in addition the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.
If you want a Linux Endpoint Device to support authorizing using authentik credentials, you will need to install the `libpam-authentik` package in addition to the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.

| Platform | Component | Description | Dependencies |
| ------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| **Linux, macOS, Windows** | `authentik-cli` | Provides commands for interacting with `authentik-agent`. | `authentik-agent` |
| **Linux, macOS, Windows** | `authentik-agent` | User service. | `authentik-sysd` |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mabye we can add a few mroe words to describe what this is

| ------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| **Linux, macOS, Windows** | `authentik-cli` | Provides commands for interacting with `authentik-agent`. | `authentik-agent` |
| **Linux, macOS, Windows** | `authentik-agent` | User service. | `authentik-sysd` |
| **Linux, macOS, Windows** | `authentik-sysd` | System service. | None |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as. above

- `sys.sock` for general communication
- `sys-ctrl.sock` for "domain" join

## Important considerations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could just be a :::warning at the top of file or something

Copy link
Member

@dominic-r dominic-r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went and did other stuff for a bit, here's files 23/31

1. Open a Terminal session and run the following command:

```
ak ssh <hostname>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ak ssh <hostname>
ak ssh <hostname>

no indent


```
ak ssh <hostname>
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```

ak ssh <hostname>
```

2. If not already authentiacted, you will be prompted for authentik credentials.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
2. If not already authentiacted, you will be prompted for authentik credentials.
2. If not already authenticated, you will be prompted for authentik credentials.


## Configure SSH authentication on an endpoint device

If you want a Linux Endpoint Device to support accepting SSH connections using authentik credentials, you will need to install the `libpam-authentik` package in addition the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mabye we can link to a reference on PAM modules idk if we have any?

Authentication is only possible if the Linux device is aware of the authentik user which is attempting to authenticate. This can be achieved in one of two ways:

1. **Provision user accounts** - Create users on the Linux device with usernames that match authentik users that need to authenticate to the device. This can be done manually or via automation tools like Ansible.
2. **`libnss-authentik`** - This is a package that can be installed on the Linux device. It is an NSS module that makes the Linux device aware of authentik users. Similar to adding a Linux device to an Active Directory or LDAP domain.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with nss. mabye a little more than "Similar to" + Active directory uses LDAP so mabye we can scratch off AD and just mention LDAP

dewi-tik and others added 3 commits December 15, 2025 09:33
Co-authored-by: Dominic R <[email protected]>
Signed-off-by: Dewi Roberts <[email protected]>
Co-authored-by: Dominic R <[email protected]>
Signed-off-by: Dewi Roberts <[email protected]>
Copy link
Contributor

@tanberry tanberry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First batch, more to come, looks great overall thaks @dewi-tik !


You can deploy the authentik agent on [Linux](./linux.md), [macOS](./macos.md), and [Windows](./windows.md) devices.

Documentation for large-scale deployments via [Mobile Device Management (MDM)](./mdm.mdx) tools is also available.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Documentation for large-scale deployments via [Mobile Device Management (MDM)](./mdm.mdx) tools is also available.
Large-scale deployments via [Mobile Device Management (MDM)](./mdm.mdx) tools is also supported.


Documentation for large-scale deployments via [Mobile Device Management (MDM)](./mdm.mdx) tools is also available.

For more information, pick a topic below:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For more information, pick a topic below:
For more information, select a topic below:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or "refer to a topic.." but "pick" feels too slangy, imo.

tags: [authentik Agent, linux, deploy, packages]
---

## What can it do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## What can it do
## What the authentik agent does

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or "What does the authentik agent do?"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still torn about the capitalization of the word "agent". A parallel is the term "Endpoint stage"... in whihc we capitalize the proper name (Endpoint) but not the word "stage". I think the same applies here, just so happens that authentik is lower-case.


## What can it do

- Retrieves information about the host for use in authentik, see [Device Compliance](../../device-compliance/index.mdx).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Retrieves information about the host for use in authentik, see [Device Compliance](../../device-compliance/index.mdx).
- Retrieves information about the host (known as "device facts") for use by authentik; see [Device Compliance](../../device-compliance/index.mdx) for more information.

dewi-tik and others added 2 commits December 16, 2025 08:38
Co-authored-by: Dominic R <[email protected]>
Signed-off-by: Dewi Roberts <[email protected]>
@rissson rissson added the backport/version-2025.12 Add this label to PRs to backport changes to version-2025.12 label Dec 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs backport/version-2025.12 Add this label to PRs to backport changes to version-2025.12

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

6 participants