[project] name = "authentik" version = "2026.5.0-rc1" description = "" authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }] requires-python = "==3.14.*" dependencies = [ "ak-guardian==3.2.0", "argon2-cffi==25.1.0", "cachetools==7.0.6", "channels==4.3.2", "cryptography==46.0.7", "dacite==1.9.2", "deepmerge==2.0", "defusedxml==0.7.1", "django-channels-postgres", "django-countries==8.2.0", "django-dramatiq-postgres", "django-filter==25.2", "django-model-utils==5.0.0", "django-pglock==1.8.0", "django-pgtrigger==4.17.0", "django-postgres-cache", "django-postgres-extra==2.0.9", "django-prometheus==2.4.1", "django-storages[s3]==1.14.6", "django-tenants==3.10.1", "django==5.2.13", "djangoql==0.19.1", "djangorestframework==3.17.1", "docker==7.1.0", "drf-orjson-renderer==1.8.0", "drf-spectacular==0.29.0", "dumb-init==1.2.5.post1", "duo-client==5.6.1", "fido2==2.2.0", "geoip2==5.2.0", "geopy==2.4.1", "google-api-python-client==2.194.0", "gssapi==1.11.1", "gunicorn==25.3.0", "jsonpatch==1.33", "jwcrypto==1.5.7", "kubernetes==35.0.0", "ldap3==2.9.1", "lxml==6.1.0", "msgraph-sdk==1.55.0", "opencontainers==0.0.15", "packaging==26.1", "paramiko==4.0.0", "psycopg[c,pool]==3.3.3", "pydantic-scim==0.0.8", "pydantic==2.13.3", "pyjwt==2.11.0", "pyrad==2.5.4", "python-kadmin-rs==0.7.0", "pyyaml==6.0.3", "requests-oauthlib==2.0.0", "scim2-filter-parser==0.7.0", "sentry-sdk==2.58.0", "service-identity==24.2.0", "setproctitle==1.3.7", "structlog==25.5.0", "swagger-spec-validator==3.0.4", "twilio==9.10.5", "ua-parser==1.0.2", "unidecode==1.4.0", "urllib3<3", "uvicorn[standard]==0.44.0", "watchdog==6.0.0", "webauthn==2.7.1", "wsproto==1.3.2", "xmlsec==1.3.17", "zxcvbn==4.5.0", ] [dependency-groups] dev = [ "aws-cdk-lib==2.250.0", "bandit==1.9.4", "black==26.3.1", "bpython==0.26", "colorama==0.4.6", "constructs==10.6.0", "coverage[toml]==7.13.5", "daphne==4.2.1", "debugpy==1.8.20", "django-stubs[compatible-mypy]==6.0.3", "djangorestframework-stubs[compatible-mypy]==3.16.9", "drf-jsonschema-serializer==3.0.0", "freezegun==1.5.5", "importlib-metadata==8.7.1", "k5test==0.10.4", "lxml-stubs==0.5.1", "mypy==1.20.1", "pdoc==16.0.0", "pytest-django==4.12.0", "pytest-flakefinder==1.1.0", "pytest-github-actions-annotate-failures==0.4.0", "pytest-randomly==4.0.1", "pytest-timeout==2.4.0", "pytest==9.0.3", "requests-mock==1.12.1", "ruff==0.15.11", "selenium==4.43.0", "types-channels==4.3.0.20260408", "types-docker==7.1.0.20260409", "types-jwcrypto==1.5.7.20260409", "types-ldap3==2.9.13.20260408", "types-requests==2.33.0.20260408", "types-zxcvbn==4.5.0.20260408", ] [tool.uv] no-binary-package = [ # This differs from the no-binary packages in the Dockerfile. This is due to the fact # that these packages are built from source for different reasons than cryptography and kadmin. # These packages are built from source to link against the libxml2 on the system which is # required for functionality and to stay up-to-date on both libraries. # The other packages specified in the dockerfile are compiled from source to link against the # correct FIPS OpenSSL libraries "lxml", "xmlsec", ] [tool.uv.sources] ak-guardian = { workspace = true } django-channels-postgres = { workspace = true } django-dramatiq-postgres = { workspace = true } django-postgres-cache = { workspace = true } opencontainers = { git = "https://github.com/vsoch/oci-python", rev = "ceb4fcc090851717a3069d78e85ceb1e86c2740c" } [tool.uv.workspace] members = [ "packages/ak-guardian", "packages/django-channels-postgres", "packages/django-dramatiq-postgres", "packages/django-postgres-cache", ] [project.scripts] ak = "lifecycle.ak:main" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.bandit] exclude_dirs = ["**/node_modules/**"] [tool.black] line-length = 100 target-version = ['py314'] exclude = 'node_modules' [tool.ruff] line-length = 100 target-version = "py314" exclude = ["**/migrations/**", "**/node_modules/**"] [tool.ruff.lint] select = [ "E", # pycodestyle "F", # Pyflakes "I", # isort "UP", # pyupgrade "B", # flake8-bugbear "DJ", # django "PL", # pylint "BLE", # flake8-blind-except ] ignore = [ "DJ001", # Avoid using `null=True` on string-based fields, "PLC0415", # `import` should be at the top-level of a file ] [tool.ruff.lint.pylint] max-args = 7 max-branches = 18 max-returns = 10 [tool.mypy] plugins = ["mypy_django_plugin.main", "mypy_drf_plugin.main", "pydantic.mypy"] [[tool.mypy.overrides]] module = ["django_tenants.*", "dramatiq.*", "pglock.*"] follow_untyped_imports = true [[tool.mypy.overrides]] module = ["cron_converter.*", "msgpack.*"] ignore_missing_imports = true [[tool.mypy.overrides]] module = [ "authentik.admin.*", "authentik.api.*", "authentik.blueprints.*", "authentik.brands.*", "authentik.core.*", "authentik.crypto.*", "authentik.endpoints.*", "authentik.enterprise.*", "authentik.events.*", "authentik.flows.*", "authentik.lib.*", "authentik.outposts.*", "authentik.policies.*", "authentik.policies.dummy.*", "authentik.policies.event_matcher.*", "authentik.policies.expiry.*", "authentik.policies.expression.*", "authentik.policies.geoip.*", "authentik.policies.password.*", "authentik.policies.reputation.*", "authentik.providers.ldap.*", "authentik.providers.oauth2.*", "authentik.providers.proxy.*", "authentik.providers.rac.*", "authentik.providers.radius.*", "authentik.providers.saml.*", "authentik.providers.scim.*", "authentik.rbac.*", "authentik.recovery.*", "authentik.root.*", "authentik.sources.kerberos.*", "authentik.sources.ldap.*", "authentik.sources.oauth.*", "authentik.sources.plex.*", "authentik.sources.saml.*", "authentik.sources.scim.*", "authentik.sources.telegram.*", "authentik.stages.authenticator_duo.*", "authentik.stages.authenticator_email.*", "authentik.stages.authenticator_sms.*", "authentik.stages.authenticator_static.*", "authentik.stages.authenticator_totp.*", "authentik.stages.authenticator_validate.*", "authentik.stages.authenticator_webauthn.*", "authentik.stages.authenticator.*", "authentik.stages.captcha.*", "authentik.stages.consent.*", "authentik.stages.deny.*", "authentik.stages.dummy.*", "authentik.stages.email.*", "authentik.stages.identification.*", "authentik.stages.invitation.*", "authentik.stages.password.*", "authentik.stages.prompt.*", "authentik.stages.redirect.*", "authentik.stages.user_delete.*", "authentik.stages.user_login.*", "authentik.stages.user_logout.*", "authentik.stages.user_write.*", "authentik.tasks.*", "authentik.tasks.schedules.*", "authentik.tenants.*", "guardian.*", "lifecycle.*", "tests.*", "tests.e2e.*", "tests.integration.*", "tests.openid_conformance.*", ] ignore_errors = true [tool.django-stubs] django_settings_module = "authentik.root.settings" [tool.coverage.run] source = ["authentik"] relative_files = true omit = [ "*/asgi.py", "manage.py", "*/migrations/*", "*/management/commands/*", "*/apps.py", # TODO: Remove this after moving website to docs "website/", "docs/", ] concurrency = ["thread", "multiprocessing"] parallel = true sigterm = true [tool.coverage.report] sort = "Cover" skip_covered = true precision = 2 exclude_lines = [ "pragma: no cover", # Don't complain about missing debug-only code: "def __unicode__", "def __str__", "def __repr__", "if self.debug", "if TYPE_CHECKING", # Don't complain if tests don't hit defensive assertion code: "raise AssertionError", "raise NotImplementedError", # Don't complain if non-runnable code isn't run: "if 0:", "if __name__ == .__main__.:", ] show_missing = true [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "authentik.root.settings" python_files = ["tests.py", "test_*.py", "*_tests.py"] junit_family = "xunit2" addopts = "-p authentik.root.test_plugin --junitxml=unittest.xml -vv --full-trace --doctest-modules --import-mode=importlib --ignore=authentik/tasks/setup.py" filterwarnings = [ "ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning", "ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning", ]