Compare commits

..

294 Commits

Author SHA1 Message Date
Marc 'risson' Schmitt
880457aadf root: add flake
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-05 16:18:12 +01:00
authentik-automation[bot]
880afb6f69 core, web: update translations (#19179)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-01-05 14:34:35 +00:00
dependabot[bot]
3142cfa050 web: bump @formatjs/intl-listformat from 8.1.0 to 8.1.1 in /web (#19182)
Bumps [@formatjs/intl-listformat](https://github.com/formatjs/formatjs) from 8.1.0 to 8.1.1.
- [Release notes](https://github.com/formatjs/formatjs/releases)
- [Commits](https://github.com/formatjs/formatjs/compare/@formatjs/intl-listformat@8.1.0...@formatjs/intl-listformat@8.1.1)

---
updated-dependencies:
- dependency-name: "@formatjs/intl-listformat"
  dependency-version: 8.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 13:23:45 +01:00
dependabot[bot]
cb2d509945 ci: bump getsentry/action-release from 3.4.0 to 3.5.0 (#19183)
Bumps [getsentry/action-release](https://github.com/getsentry/action-release) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/getsentry/action-release/releases)
- [Changelog](https://github.com/getsentry/action-release/blob/master/CHANGELOG.md)
- [Commits](128c5058bb...dab6548b3c)

---
updated-dependencies:
- dependency-name: getsentry/action-release
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 12:52:05 +01:00
dependabot[bot]
9a8c7bb23b web: bump knip from 5.78.0 to 5.79.0 in /web (#19181)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.78.0 to 5.79.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@5.79.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.79.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 12:51:33 +01:00
Duncan Tasker
bc24815ae6 lifecycle: fix migration conn_options for psycopg connection (#19134)
fix migrations to add conn_options to the psycopg connection
2026-01-05 09:58:40 +01:00
Adithya S Narasinghe
c3eb401600 website/docs: remove duplicates in slo docs (#19170)
remove duplicated points in the iframe mode points in slo docs
2026-01-04 18:28:12 +00:00
Jens L.
1dd7269871 web/admin: adjust sync threshold, add tooltip (#19131)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-01-03 23:00:59 -05:00
Teffen Ellis
38381504c0 web: Fix user library colors, modal z-indexes, table progress bars (#19152)
* Fix progress bar fade out, positioning, labels.

* Export parts. Fix z-index, colors.

* Fix clickable area.

* Ignore clickable icons.
2026-01-03 22:43:31 -05:00
Dominic R
366ebc3a91 web: fix slug auto-updating when editing existing applications (#19169) 2026-01-04 03:36:13 +00:00
Dominic R
32f77f05f4 core: handle deserialization errors from FileField migration (#19067)
after migration 0054 changed icon fields from Django FileField to a TextField based custom FileField, old sessions which had serialized Source/Application model instances fail to deserialize.

The old FieldFile descriptors try to access field.storage which no longer exists.

We can't edit that migration since it has already been ran by many/

So, you  add AttributeError and TypeError to exception handling in SessionStore.decode() to return an empty session instead of crashing with 500.
2026-01-03 15:32:22 -05:00
authentik-automation[bot]
f077506c41 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#19137)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-01-03 21:30:16 +01:00
William Kray
9183e4967b website/integrations: vaultwarden: add custom email scope (#19160)
* update vaultwarden integration doc to include custom scope mapping needed

* wip

---------

Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-03 02:31:54 +00:00
dependabot[bot]
af5bff725f ci: bump int128/docker-manifest-create-action from 2.10.0 to 2.12.0 (#19138)
Bumps [int128/docker-manifest-create-action](https://github.com/int128/docker-manifest-create-action) from 2.10.0 to 2.12.0.
- [Release notes](https://github.com/int128/docker-manifest-create-action/releases)
- [Commits](b60433fd43...6cdd53a833)

---
updated-dependencies:
- dependency-name: int128/docker-manifest-create-action
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-02 11:57:48 +01:00
authentik-automation[bot]
1c2cf4bb5c core, web: update translations (#19135)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-01-02 11:33:11 +01:00
dependabot[bot]
c6a5062cd7 web: bump globals from 16.5.0 to 17.0.0 in /web (#19154)
Bumps [globals](https://github.com/sindresorhus/globals) from 16.5.0 to 17.0.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.5.0...v17.0.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-02 11:32:59 +01:00
Jens L.
e4127fdaf0 web/user: fix consent delete form missing details (#19147)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-01-02 11:31:11 +01:00
Teffen Ellis
7085743cac web: Token Form Fixes (#19121)
* Fix autofocus attribute.

* web: Fix label alignment, focus handlers, edit states.

* Tidy date functions.

* Use Dates over strings.
2026-01-01 19:03:57 -05:00
Jens L.
27bd6d6e92 website/docs: fix build (#19148)
* ensure we never throw errors in the browser

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* cleaner

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix misleading variable

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Tidy behavior.

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2026-01-01 18:30:27 +00:00
Jens L.
fecd852c2b website/docs: endpoint agent release notes (#19042)
* website/docs: endpoint agent release notes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* rename, update

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-01 13:53:54 +01:00
Dominic R
c45110ed10 web: disable user settings fields when changes are not allowed (#19132)
* web: disable user settings fields when changes are not allowed

Previously, when admins disabled the ability to change email, username, or name in System Settings, users would only see an error after attempting to submit the form.

Now, the field is marked as disabled.

* wip
2025-12-31 16:00:09 -05:00
SKAL
b1bfd457eb website/integrations: karakeep: don't hardcode wellknown's slug (#19127)
* Clarify OAUTH_WELLKNOWN_URL in documentation

Updated OAUTH_WELLKNOWN_URL placeholder for clarity.
I've updated it because this way it copies also the correct slug (which might be changed)

Signed-off-by: SKAL <pippo73@users.noreply.github.com>

* Update website/integrations/documentation/karakeep/index.md

Signed-off-by: Dominic R <dominic@sdko.org>

---------

Signed-off-by: SKAL <pippo73@users.noreply.github.com>
Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-31 16:06:14 +01:00
dependabot[bot]
8b579f064e core, web: bump qs from 6.14.0 to 6.14.1 in /packages/docusaurus-config (#19130)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.0 to 6.14.1.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-31 15:53:02 +01:00
dependabot[bot]
79b01f8c56 core: bump library/node from ccfd9da to 03729a7 in /website (#19125)
Bumps library/node from `ccfd9da` to `03729a7`.

---
updated-dependencies:
- dependency-name: library/node
  dependency-version: 25.2.1-trixie
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-31 15:08:32 +01:00
dependabot[bot]
d7e4b5c1ed core: bump github.com/jackc/pgx/v5 from 5.7.6 to 5.8.0 (#19088)
Bumps [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) from 5.7.6 to 5.8.0.
- [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v5.7.6...v5.8.0)

---
updated-dependencies:
- dependency-name: github.com/jackc/pgx/v5
  dependency-version: 5.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-31 15:08:21 +01:00
dependabot[bot]
8b2ccc9568 web: bump the swc group across 1 directory with 11 updates (#19124)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc).


Updates `@swc/core` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-darwin-arm64` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-darwin-x64` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-linux-arm-gnueabihf` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-linux-arm64-gnu` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-linux-arm64-musl` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-linux-x64-gnu` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-linux-x64-musl` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-win32-arm64-msvc` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-win32-ia32-msvc` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

Updates `@swc/core-win32-x64-msvc` from 1.15.7 to 1.15.8
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.7...v1.15.8)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.15.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-31 14:57:41 +01:00
dependabot[bot]
84fa4f9dc8 core: bump library/nginx from ad85427 to ca871a8 in /website (#19126)
Bumps library/nginx from `ad85427` to `ca871a8`.

---
updated-dependencies:
- dependency-name: library/nginx
  dependency-version: 1.29-trixie
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-31 14:06:53 +01:00
Teffen Ellis
e5852b46cb web: Capitalize language display names, code owner fix (#19119)
* web: Capitalize locale display names.

* Fix broad code owner.
2025-12-30 18:04:36 -05:00
Teffen Ellis
c108877a85 web: Fix Impersonation, Lit Reactive Controller Contexts (#19114)
* web: Fix issue where impersonation does not trigger updates.

* web: Fix issues surrounding abort controller types, lifecycle.
2025-12-30 11:37:09 -05:00
dependabot[bot]
84f74ee306 web: bump the eslint group across 1 directory with 3 updates (#19110)
Bumps the eslint group with 3 updates in the /web directory: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@typescript-eslint/eslint-plugin` from 8.50.1 to 8.51.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.51.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.50.1 to 8.51.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.51.0/packages/parser)

Updates `typescript-eslint` from 8.50.1 to 8.51.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.51.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-30 13:08:21 +01:00
dependabot[bot]
32cb16ec2f core: bump library/nginx from fb01117 to ad85427 in /website (#19112)
Bumps library/nginx from `fb01117` to `ad85427`.

---
updated-dependencies:
- dependency-name: library/nginx
  dependency-version: 1.29-trixie
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-30 13:08:08 +01:00
dependabot[bot]
d5a2f2dc14 web: bump the storybook group across 1 directory with 5 updates (#19111)
Bumps the storybook group with 4 updates in the /web directory: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links), [@storybook/web-components](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/web-components) and [@storybook/web-components-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/web-components-vite).


Updates `@storybook/addon-docs` from 10.1.10 to 10.1.11
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.11/code/addons/docs)

Updates `@storybook/addon-links` from 10.1.10 to 10.1.11
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.11/code/addons/links)

Updates `@storybook/web-components` from 10.1.10 to 10.1.11
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.11/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.1.10 to 10.1.11
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.11/code/frameworks/web-components-vite)

Updates `storybook` from 10.1.10 to 10.1.11
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.11/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-30 13:07:56 +01:00
Dominic R
c22f043ad6 website/docs: release notes: Add more integrations (#19109) 2025-12-30 08:10:31 +00:00
Lukas
b2778702d0 website/integrations: Add Audiobookshelf (#19104)
* website/docs: Add Audiobookshelf Community Integration Documentation

* wip

---------

Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-30 03:22:54 +00:00
Lukas
d43724deb8 website/integrations: Add Pulse (#19105)
* website/docs: Add Pulse Community Integration Documentation

* wip

---------

Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-30 03:19:03 +00:00
Ken Sternberg
8b21392aa3 web/maintenance/no unknown attributes (part 1) (#18970)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

- Bugfix: adds the InvalidationFlow to the Radius Provider dialogues
  - Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated
    to the Notification.
- Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/`

## Note

Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.

* This (temporary) change is needed to prevent the unit tests from failing.

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

* Revert "This (temporary) change is needed to prevent the unit tests from failing."

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

Fixes bad escaping of URLs in the release notes that resulted in mangled output.

v2024.6.4 had entries that looked like this:

```
##### `GET` /providers/google_workspace/{#123;id}#125;/
```

v2025.4.md had entries that looked like this:

```
##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/
```

A couple of straightforward search-and-replaces has fixed the issue.

## Notes

Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past,
but it was obvious when visiting the page.

@Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while
Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd
behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues
in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"),
which could be very confusing.

The inconsistencies between the two releases, and the working releases, suggests that the error was
introduced manually.

* web/maintenance/no-unknown-attributes-1

# What

This commit is a collection of fixes and adaptations discovered while running lit-analyzer in a stricter role than usual. These fixes are to 9 of the existing issues; there are 16 more that will be addressed in the next two pull requests.

The following issues were uncovered.

- `ak-slug-input` does not take `autocomplete`.
- `ak-wizard-page-type-create` does not take, or use, the `name` attribute. It also has no `value` of its own, so it is not processed as a form object.
- `ak-endpoints-device-access-groups-form` does not take a `pk` attribute. It takes an `.instancePk` property.
- `ak-provider-oauth2-redirect-uri` is only used in one place, and that place uses the term `input-id` for the key. The component was expected `inputId`. Since it is a string and therefore an attribute, kebab-case is the appropriate fix here.
- `input-mode` is not a valid attribute. The attribute is `inputmode`, and the property is `inputMode`. It may not be undefined. If it is defined, the default is `text`. I have fixed this in the attribute and in the two Forms that used it.
- `form-associated-element` had both `name` and `type` as readonly. Since they are native attributes, they can be attributes or they can be readonly. They can’t be both. I have made them read-write.
- `user-source-settings-page` is only used in one place, and that place uses the term `input-id` for the key. The component was expected `inputId`. Since it is a string and therefore an attribute, kebab-case is the appropriate fix here.

These guideposts will be placed on the PR.

* Update web/src/admin/providers/oauth2/OAuth2ProviderRedirectURI.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>

* Update web/src/components/ak-text-input.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>

* Fix duplicate lines in property declaration. Not sure how that snuck in there.

* Prettier has opinions.

---------

Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-12-29 10:50:16 -08:00
Austin Dudzik
7246681815 Update Vaultwarden documentation by removing warning (#19102)
Removed warning about SSO feature availability on testing images.

Signed-off-by: Austin Dudzik <65981261+austin-dudzik@users.noreply.github.com>
2025-12-29 18:01:49 +00:00
Dominic R
d5869674b7 web: Fix stale flow background (#19015) 2025-12-29 17:30:12 +00:00
Dominic R
2a97dd2912 web: fix promoted source button hover losing blue color (#19048) 2025-12-29 08:59:53 -08:00
dependabot[bot]
485d413548 web: bump knip from 5.77.1 to 5.78.0 in /web (#19090) 2025-12-29 17:55:01 +01:00
Dewi Roberts
cef54653ec website/docs: endpoint devices: add path to macos setup (#19093)
* Add path

* Update macos.md

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
2025-12-29 08:15:36 -05:00
Dewi Roberts
e16a0ab99d website/docs: endpoint devices: update features table (#19094)
* Update table

* Remove wording
2025-12-29 08:14:15 -05:00
Dominic R
722b425c3b website/docs: rel notes .12: add wallos (#19063) 2025-12-29 09:24:26 +00:00
Dominic R
ec227532c4 website/docs: endpoints: mention connector key required for stage to work (#19084)
keypair = CertificateKeyPair.objects.filter(pk=stage.connector.challenge_key_id).first()
  if not keypair:
      return self.executor.stage_ok()  # < --- skips the stage

took me a bit of time to find this and yea
2025-12-29 09:23:15 +00:00
Jens L.
7300737a12 web/admin: fix button alignment on user view page (#19079)
* web/admin: fix button alignment on user view page

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix width

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-28 16:15:18 +01:00
dependabot[bot]
9d70c5a3ed web: bump @formatjs/intl-listformat from 7.7.13 to 8.1.0 in /web (#19054)
* web: bump @formatjs/intl-listformat from 7.7.13 to 8.1.0 in /web

Bumps [@formatjs/intl-listformat](https://github.com/formatjs/formatjs) from 7.7.13 to 8.1.0.
- [Release notes](https://github.com/formatjs/formatjs/releases)
- [Commits](https://github.com/formatjs/formatjs/compare/@formatjs/intl-listformat@7.7.13...@formatjs/intl-listformat@8.1.0)

---
updated-dependencies:
- dependency-name: "@formatjs/intl-listformat"
  dependency-version: 8.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-28 15:19:53 +01:00
Jens L.
b5848765b2 internal: update TLS Suite (#19076)
* internal: update TLS Suite

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* disable chacha20 due to fips

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-28 14:46:27 +01:00
dependabot[bot]
1877a9d286 web: bump @lit/localize-tools from 0.8.0 to 0.8.1 in /web (#19040)
Bumps [@lit/localize-tools](https://github.com/lit/lit/tree/HEAD/packages/localize-tools) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/lit/lit/releases)
- [Changelog](https://github.com/lit/lit/blob/main/packages/localize-tools/CHANGELOG.md)
- [Commits](https://github.com/lit/lit/commits/HEAD/packages/localize-tools)

---
updated-dependencies:
- dependency-name: "@lit/localize-tools"
  dependency-version: 0.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 14:00:39 +01:00
dependabot[bot]
a5ed6b05e2 web: bump knip from 5.77.0 to 5.77.1 in /web (#19049)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.77.0 to 5.77.1.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@5.77.1/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.77.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 14:00:20 +01:00
authentik-automation[bot]
5d66ba341d core, web: update translations (#18991)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-28 14:00:00 +01:00
Jens L.
4f4e37f2b0 tests/e2e: add endpoint tests (#19072)
* tests/e2e: add endpoint tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* dont rely on hostname

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-28 12:53:00 +01:00
ocmateusz
a98d70ea7b website/integrations: owncloud: fix php (#19073) 2025-12-27 21:45:35 +00:00
Dominic R
aa8644ea27 web: fix file search input not resetting results properly (#19034) 2025-12-27 16:19:50 -05:00
Dominic R
fe0106886a blueprints: fix deadlock and task context error in MetaApplyBlueprint (#19033)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-26 13:47:07 -05:00
authentik-automation[bot]
0a27684dbd *: Auto compress images (#19065)
*: compress images

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dominic-r <157558804+dominic-r@users.noreply.github.com>
2025-12-26 12:55:08 -05:00
Borut Mrak
25cae250cd website/docs: FreeIPA documentation updates (#15183)
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Borut Mrak <borut.mrak@acex.si>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-12-26 17:14:59 +00:00
Lukas
960d09300f website/integrations: Add Wallos (#19013)
Co-authored-by: dewi-tik <dewi@goauthentik.io>
2025-12-26 11:50:31 -05:00
Jens L.
46297698d6 blueprints: set enrollment token key (#19061)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-26 17:37:48 +01:00
Jens L.
448c8f8745 endpoints/devices: cleanup (#19047)
* endpoints: make device token internally managed

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix text and defaults for agent

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-org some code

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-26 14:20:20 +01:00
Amélie Lilith Krejčí
1685f8f9eb lib/sync: fix sync_dispatch (#19053)
* fix: add missing call to all on self.schedules

Fixes #19051

* fix: change the name of syncOutgoingTriggerMode ak-radio-input

Fixes #19052
2025-12-26 14:01:27 +01:00
Marcelo Elizeche Landó
61e45ca59a docs/release notes: update 2025.12 release notes (#19043)
* Add links and tags

* Update website/docs/releases/2025/v2025.12.md

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>

---------

Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-24 13:01:26 -03:00
dependabot[bot]
0a5e421c9c core: bump goauthentik.io/api/v3 from 3.2026020.5 to 3.2026020.6 (#19036)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2026020.5 to 3.2026020.6.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2026020.5...v3.2026020.6)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2026020.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-24 13:58:28 +01:00
dependabot[bot]
097b39d773 web: bump knip from 5.75.1 to 5.77.0 in /web (#19041)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.75.1 to 5.77.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@5.77.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.77.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-24 13:56:04 +01:00
dependabot[bot]
a9c2613ba6 web: bump @lit/reactive-element from 2.1.1 to 2.1.2 in /web (#19037)
Bumps [@lit/reactive-element](https://github.com/lit/lit/tree/HEAD/packages/reactive-element) from 2.1.1 to 2.1.2.
- [Release notes](https://github.com/lit/lit/releases)
- [Changelog](https://github.com/lit/lit/blob/main/packages/reactive-element/CHANGELOG.md)
- [Commits](https://github.com/lit/lit/commits/lit@2.1.2/packages/reactive-element)

---
updated-dependencies:
- dependency-name: "@lit/reactive-element"
  dependency-version: 2.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-24 13:55:39 +01:00
dependabot[bot]
4684f8a4c4 web: bump lit from 3.3.1 to 3.3.2 in /web (#19038)
Bumps [lit](https://github.com/lit/lit/tree/HEAD/packages/lit) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/lit/lit/releases)
- [Changelog](https://github.com/lit/lit/blob/main/packages/lit/CHANGELOG.md)
- [Commits](https://github.com/lit/lit/commits/lit-element@3.3.2/packages/lit)

---
updated-dependencies:
- dependency-name: lit
  dependency-version: 3.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-24 13:54:36 +01:00
Dominic R
7845dcf687 web/admin: use consistent icon for inactive user status (#19032) 2025-12-24 03:23:16 +00:00
Jens L.
cc2dbf4db5 core: use chunked_queryset for expired message deletion (#19028)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-24 01:15:45 +01:00
Teffen Ellis
f520c90e16 web: Fix Storybook package resolution with npm link (#19016) 2025-12-23 18:40:47 +00:00
Teffen Ellis
342d9eb726 web: Locale selector UI fixes (#18972)
* Fix alignment, focus.

* Clean up.

* Tidy click area.

* Fix compatibility mode.

* Fix alignment.

* Fix issues surrounding labels, alignment, consistency.

* Update web/src/common/ui/locale/format.ts

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>

* Tidy hover states.

* Tidy.

* Clean up parsing.

* Tidy comments, usage.

* Always use script naming over region.

* Remove unused.

* Spacing.

---------

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-12-23 18:40:02 +00:00
Teffen Ellis
2f8dfff207 website/docs: Prioritize "Release Candidate" over "Current Release" (#18975)
Normalize labels.
2025-12-23 15:41:42 +01:00
dependabot[bot]
0ba071e1e5 core: bump goauthentik.io/api/v3 from 3.2026020.4 to 3.2026020.5 (#19017)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2026020.4 to 3.2026020.5.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2026020.4...v3.2026020.5)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2026020.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-23 14:50:54 +01:00
dependabot[bot]
6443d56d2b web: bump the eslint group across 1 directory with 3 updates (#19019)
Bumps the eslint group with 3 updates in the /web directory: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@typescript-eslint/eslint-plugin` from 8.50.0 to 8.50.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.50.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.50.0 to 8.50.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.50.1/packages/parser)

Updates `typescript-eslint` from 8.50.0 to 8.50.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.50.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.50.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.50.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.50.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-23 13:43:43 +01:00
Jens L.
fcc0438961 web/admin: prevent file upload attempt when backend not managed (#18646)
* web/admin: prevent file upload attempt when backend not managed

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* wip

* fixup

* rework

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add check for reports

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix delete table for data exports missing details

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-23 13:41:27 +01:00
Jens L.
d9cd1096b9 api: rework schema generation (#18977)
* api: rework schema generation

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better error message

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* actually fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* different test

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-23 12:20:24 +01:00
dependabot[bot]
fc4765d265 web: bump globby from 16.0.0 to 16.1.0 in /web (#18995)
Bumps [globby](https://github.com/sindresorhus/globby) from 16.0.0 to 16.1.0.
- [Release notes](https://github.com/sindresorhus/globby/releases)
- [Commits](https://github.com/sindresorhus/globby/compare/v16.0.0...v16.1.0)

---
updated-dependencies:
- dependency-name: globby
  dependency-version: 16.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-23 11:50:15 +01:00
dependabot[bot]
4b54684efa core: bump openapitools/openapi-generator-cli from v7.16.0 to v7.18.0 in /scripts/api (#19018)
core: bump openapitools/openapi-generator-cli in /scripts/api

Bumps openapitools/openapi-generator-cli from v7.16.0 to v7.18.0.

---
updated-dependencies:
- dependency-name: openapitools/openapi-generator-cli
  dependency-version: v7.18.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-23 11:49:39 +01:00
dependabot[bot]
519958fdbc web: bump the rollup group across 1 directory with 4 updates (#18994)
Bumps the rollup group with 4 updates in the /web directory: [@rollup/rollup-darwin-arm64](https://github.com/rollup/rollup), [@rollup/rollup-linux-arm64-gnu](https://github.com/rollup/rollup), [@rollup/rollup-linux-x64-gnu](https://github.com/rollup/rollup) and [rollup](https://github.com/rollup/rollup).


Updates `@rollup/rollup-darwin-arm64` from 4.53.5 to 4.54.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.5...v4.54.0)

Updates `@rollup/rollup-linux-arm64-gnu` from 4.53.5 to 4.54.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.5...v4.54.0)

Updates `@rollup/rollup-linux-x64-gnu` from 4.53.5 to 4.54.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.5...v4.54.0)

Updates `rollup` from 4.53.5 to 4.54.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.5...v4.54.0)

---
updated-dependencies:
- dependency-name: "@rollup/rollup-darwin-arm64"
  dependency-version: 4.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-arm64-gnu"
  dependency-version: 4.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-version: 4.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rollup
- dependency-name: rollup
  dependency-version: 4.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rollup
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 23:32:24 +01:00
Dewi Roberts
97ef6a5eb2 website/docs: improve endpoint devices docs (#19007)
* Remove sudo auth sections

* Add firefox extension link

* Add chrome extension

* Update release notes

* Remove link

* Fix link

* Fix release note wording
2025-12-22 20:32:20 +00:00
Jens L.
e23c28373c enterprise/search: add static autocomplete structure (#19008)
* enterprise/search: add static autocomplete structure

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add recursive structured for context

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-22 20:53:33 +01:00
Jens L.
7fa28c60c7 enterprise/reports: improve export list, confirmation (#18981)
* enterprise/reports: use verbose name for model label

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add confirmation for export

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove duplicated api

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix duplicate

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix search query not updated

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* exclude page & page size

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* improve query display

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix user display

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* exclude unset params

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Apply suggestions from code review

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Jens L. <jens@beryju.org>

* more code style

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix types

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-12-22 20:35:18 +01:00
Jens L.
3cd1a31365 providers/oauth2: Automated OpenID Conformance tests (#14785)
* some progress

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* somewhat working?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove some previous debugging things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make it kinda work

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* revert more debugging stuff

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make tests mostly work

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* smaller screenshots?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove debugging

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* sleep a bit before checking again

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* cleanup, restart loop when we finished an operation

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* refactor conformance helper to requests (thanks chatgpt)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* properly install subtests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* maybe run in CI?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* dont hardcode IP

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix network and cookie deletion

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* upload cert exports

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* test

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* attempt to switch to generated

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make it work generated?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix teardown

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-add implicit and fix?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Revert "re-add implicit and fix?"

This reverts commit 6a4d15fc22cf4b27ffa428be9ecc9a0e778961c6.

* Revert "fix teardown"

This reverts commit cb96b0cb988acedec1fe72ec437b68e2c38ed6b1.

* Revert "make it work generated?"

This reverts commit 4e29d2c5737ee9aaad6c0f4701caf7e0fb110e15.

* Revert "attempt to switch to generated"

This reverts commit 6f851e021d305a93be9cfbb4a9b6783231b6d7cf.

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* check authorize request param earlier

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix some

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix basic suite?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* another actual fix; don't return access_token when using response_type id_token

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add implicit test

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add custom profile scope that includes standard scopes to return number of warnings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use actual timestamp

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing offline_access, use scoped issuer

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* only run basic+implicit for now, fix other tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* split up

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix offline_access tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix waiting for compete on error

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix duplicate artifact

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix artifact

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* 👀

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix typing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* typing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix implicit

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't wait for conformance test

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more disk space

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-22 20:21:22 +01:00
dependabot[bot]
b66e4074e2 ci: bump docker/setup-buildx-action from 3.11.1 to 3.12.0 (#18999)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.11.1 to 3.12.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](e468171a9d...8d2750c68a)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 16:34:46 +01:00
Jens L.
fa65d4730c blueprints: fix flaky tests (#19002)
* blueprints: attempt to fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix postgres debug logging

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-22 15:39:05 +01:00
Dominic R
e3d774b3ef web: fix Open button selecting row instead of navigating (#18992)
the `isEventTargetingListener()` function only checked the click target and the immediate parent for interactive elements (like links, buttons and more). when clicking the icon inside the Open button, the DOM structure is:

<a href=...>  <--- 2 levels up, never checked
<pf-tooltip>  <--- immediate parent, not interactive
<i> <---- click target, not interactive

Because <i> and <pf-tooltip> did not match the interactive elements query, the function returned false which caused the table rowClickListener to continue with row selection isntead of allowing the click.

The fix is to update the function to to traverse (up) the entire dom tree from the click target to the listener element (the table cell) and check for each ancestor for the interactive elements.
2025-12-22 15:02:59 +01:00
Jens L.
162e05ff9d events: notifications live update (#18980)
* this has been broken for a while but no one noticed...? cc @rissson

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* send WS broadcast for new notifications

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better layout

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix e2e tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-21 23:02:56 +01:00
Henry Skrtich
238cefba45 web/admin: Fix haveibeenpwned link in PasswordPolicyForm (#18984)
web: Fix haveibeenpwned link in PasswordPolicyForm
2025-12-21 15:02:04 +01:00
Jens L.
bfa46ddc7e web/admin: fix dark theme on map (#18985)
web/admin: fix dark theme on map broken

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-21 15:01:26 +01:00
Jens L.
e24b60f806 blueprints: add InternallyManagedMixin instead of large list (#18983)
* blueprints: add InternallyManagedMixin instead of large list

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix recursion

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix e2e tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-21 03:31:41 +01:00
Stijn van Hees
0c792c8463 website/integrations: Fix path for Cloudflare Access (#18979)
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-20 16:57:57 +00:00
Ken Sternberg
1143de97d0 web/maintenance: no unknown tag names (#18944)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

- Bugfix: adds the InvalidationFlow to the Radius Provider dialogues
  - Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated
    to the Notification.
- Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/`

## Note

Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.

* This (temporary) change is needed to prevent the unit tests from failing.

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

* Revert "This (temporary) change is needed to prevent the unit tests from failing."

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

Fixes bad escaping of URLs in the release notes that resulted in mangled output.

v2024.6.4 had entries that looked like this:

```
##### `GET` /providers/google_workspace/{#123;id}#125;/
```

v2025.4.md had entries that looked like this:

```
##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/
```

A couple of straightforward search-and-replaces has fixed the issue.

## Notes

Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past,
but it was obvious when visiting the page.

@Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while
Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd
behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues
in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"),
which could be very confusing.

The inconsistencies between the two releases, and the working releases, suggests that the error was
introduced manually.

* web/maintenance: lint pass to fix broken or unrecognized tag names

# What

This code removes two places in the code that referenced obsolete tag names.

In AkWizardFormPage, the case was a tag that was defined but never used. It, in turn, referenced a tag that did not exist.

In AkApplicationWizard’s ProviderChoices, we referenced eight custom components that did not exist and were never defined anywhere in the code. The references to `renderers` were obsolete; despite being defined they were never used. (This lack of use was covered up by lots of `export`s discarding Typescript’s check against unused field.)

- [x] The code has been formatted

# Why

- WizardFormPage references ‘ak-wizard-form’, which does not exist
- No other component imports, inherits, or extends WizardFormPage. It only exists by itself.

``` shell
$ rg 'WizardFormPage'
src/elements/wizard/WizardFormPage.ts
39:export class WizardFormPage extends WizardPage {
```

- The objects referenced here in these renderers do not exist.
- Without them, the priority ordering code becomes much simpler
- No LocalTypeCreate calls are needed; just use the default API TypeCreate types now

<!-- -->

    ./src/admin/applications/wizard/steps/ProviderChoices.ts

        Unknown tag <ak-application-wizard-authentication-by-oauth>. Did you mean <ak-application-wizard-application-step>?
        19:  html`<ak-application-wizard-authentication-by-oauth></ak-appl
        no-unknown-tag-name

        Unknown tag <ak-application-wizard-authentication-by-saml-configuration>. Did you mean <ak-application-wizard-application-step>?
        24:  html`<ak-application-wizard-authentication-by-saml-configuration></ak-appl
        no-unknown-tag-name

* A better way to prioritise, without losing the original script.

* further streamlining the priority order.

* Making it harder to add bad provider models to the application wizard.

* Prettier has opinions.

* Adding these to the tagname maps so lit-analyze can validate their uses.

* Made testing for valid returns from the server generic.  Suggestion from @GirlBossRush
2025-12-19 08:20:33 -08:00
Ken Sternberg
d6bc5871fa web/maintenance: fix missing custom web component imports (#18942)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

- Bugfix: adds the InvalidationFlow to the Radius Provider dialogues
  - Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated
    to the Notification.
- Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/`

## Note

Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.

* This (temporary) change is needed to prevent the unit tests from failing.

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

* Revert "This (temporary) change is needed to prevent the unit tests from failing."

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

Fixes bad escaping of URLs in the release notes that resulted in mangled output.

v2024.6.4 had entries that looked like this:

```
##### `GET` /providers/google_workspace/{#123;id}#125;/
```

v2025.4.md had entries that looked like this:

```
##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/
```

A couple of straightforward search-and-replaces has fixed the issue.

## Notes

Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past,
but it was obvious when visiting the page.

@Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while
Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd
behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues
in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"),
which could be very confusing.

The inconsistencies between the two releases, and the working releases, suggests that the error was
introduced manually.

* web: lint pass to add all missing custom component imports

# What

The latest version of lit-analyze found 53(!) places in the codebase where we referenced a custom web component but not guarantee that it had been registered with the browser. Most of these are so commonplace that they had already been pulled in and registered elsewhere, but it’s still bad practice to leave these out.

* web/maintenance: lint pass to fix broken or unrecognized tag names

# What

This code removes two places in the code that referenced obsolete tag names.

In AkWizardFormPage, the case was a tag that was defined but never used. It, in turn, referenced a tag that did not exist.

In AkApplicationWizard’s ProviderChoices, we referenced eight custom components that did not exist and were never defined anywhere in the code. The references to `renderers` were obsolete; despite being defined they were never used. (This lack of use was covered up by lots of `export`s discarding Typescript’s check against unused field.)

- [x] The code has been formatted

# Why

- WizardFormPage references ‘ak-wizard-form’, which does not exist
- No other component imports, inherits, or extends WizardFormPage. It only exists by itself.

``` shell
$ rg 'WizardFormPage'
src/elements/wizard/WizardFormPage.ts
39:export class WizardFormPage extends WizardPage {
```

- The objects referenced here in these renderers do not exist.
- Without them, the priority ordering code becomes much simpler
- No LocalTypeCreate calls are needed; just use the default API TypeCreate types now

<!-- -->

    ./src/admin/applications/wizard/steps/ProviderChoices.ts

        Unknown tag <ak-application-wizard-authentication-by-oauth>. Did you mean <ak-application-wizard-application-step>?
        19:  html`<ak-application-wizard-authentication-by-oauth></ak-appl
        no-unknown-tag-name

        Unknown tag <ak-application-wizard-authentication-by-saml-configuration>. Did you mean <ak-application-wizard-application-step>?
        24:  html`<ak-application-wizard-authentication-by-saml-configuration></ak-appl
        no-unknown-tag-name

* Revert "web/maintenance: lint pass to fix broken or unrecognized tag names"

This reverts commit e9e073fbcc.
2025-12-19 08:19:25 -08:00
Dewi Roberts
0fec6fb81b website/docs: add note to active directory source doc (#18787)
Adds note

Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-19 16:00:17 +01:00
dependabot[bot]
f8cf6b8530 ci: bump actions/attest-build-provenance from 3.0.0 to 3.1.0 (#18960)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](977bb373ed...00014ed6ed)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 15:57:50 +01:00
dependabot[bot]
320ca655b4 web: bump @sentry/browser from 10.31.0 to 10.32.0 in /web in the sentry group across 1 directory (#18957)
web: bump @sentry/browser in /web in the sentry group across 1 directory

Bumps the sentry group with 1 update in the /web directory: [@sentry/browser](https://github.com/getsentry/sentry-javascript).


Updates `@sentry/browser` from 10.31.0 to 10.32.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.31.0...10.32.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 10.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 15:57:32 +01:00
dependabot[bot]
72dd63af31 web: bump the swc group across 1 directory with 11 updates (#18958)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc).


Updates `@swc/core` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-darwin-arm64` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-darwin-x64` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-linux-arm-gnueabihf` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-linux-arm64-gnu` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-linux-arm64-musl` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-linux-x64-gnu` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-linux-x64-musl` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-win32-arm64-msvc` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-win32-ia32-msvc` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

Updates `@swc/core-win32-x64-msvc` from 1.15.6 to 1.15.7
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.6...v1.15.7)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.15.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 15:47:34 +01:00
dependabot[bot]
4cbffd5b27 web: bump chromedriver from 143.0.2 to 143.0.3 in /web (#18959)
Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 143.0.2 to 143.0.3.
- [Commits](https://github.com/giggio/node-chromedriver/compare/143.0.2...143.0.3)

---
updated-dependencies:
- dependency-name: chromedriver
  dependency-version: 143.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 15:29:13 +01:00
dependabot[bot]
4d9f4638fa core: bump goauthentik.io/api/v3 from 3.2026020.3 to 3.2026020.4 (#18956)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2026020.3 to 3.2026020.4.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2026020.3...v3.2026020.4)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2026020.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 11:36:45 +00:00
Connor Peshek
646a0d3692 root: move docker files to lifecycle/containers and change docker-compose to compose (#16624) 2025-12-18 18:23:54 -06:00
Dominic R
59460ac840 flows/executor: fix KeyError when session has no existing plan (#18951) 2025-12-19 00:21:32 +00:00
Jens L.
c67cd41c22 web/admin: fix endpoints user binding (#18935)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-19 00:07:52 +01:00
Teffen Ellis
5a840e8f6d website/docs: Fix version parsing. (#18948) 2025-12-18 21:59:48 +00:00
Dewi Roberts
81771ce609 website/docs: release notes: add endpoint device links to 2025.12 notes (#18940)
Add links to release notes
2025-12-18 20:16:59 +00:00
Teffen Ellis
5babdf64cb website/docs: Fix labels, Pre-Release detection (#18945)
* Fix import path.

* Show unlisted entries if release.

* Fix sidebar rendering.

* Fix positioning of pre-release note. Tidy phrasing.

* Clarify pre-release vs draft.
2025-12-18 21:01:47 +01:00
Dewi Roberts
de298d72de website/docs: endpoint devices (#18634)
* Initial

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Apply suggestions

* Apply suggestions

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* WIP

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* fixes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* WIP

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

* Fix anchor

* Update website/docs/endpoint-devices/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* WIP

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-18 19:09:11 +00:00
Jens L.
d7f3055cd8 stages/identification: replace sleep with make_password (#18883)
* stages/identification: replace sleep with make_password

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-18 19:09:10 +01:00
Jens L.
0beb8f9a12 web/elements: progress-bar and table loading header (#18934)
* add ak-progress-bar

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make intermediate smaller

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add table

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* hide table overflow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-18 17:44:21 +01:00
Marc 'risson' Schmitt
6bfa07d761 crypto: fix extra cert data in db migration (#18937) 2025-12-18 16:34:31 +00:00
Weilu Jia
8d4c05c7e7 website/integrations: Add launch URL for Immich (#18921)
Signed-off-by: Weilu Jia <optix2000@users.noreply.github.com>
2025-12-18 16:24:51 +00:00
Victor Nawothnig
598b92d928 web/flow: Fix spurious double submit on ak-stage-autosubmit (#18727)
* Fix double submission on ak-stage-autosubmit

* use updated correctly

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Victor Nawothnig <Victor.Nawothnig+git@icloud.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-18 14:35:18 +00:00
Simonyi Gergő
90c8f23862 packages/ak-guardian: cast safely (#18929)
* packages/ak-guardian: cast safely

* use `regexp_like` instead of `pg_input_is_valid`

* alternative approach: RawSQL subquery

* remove extra fields we don't need

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* prevent subquery collapse

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* take into account foreignkeys

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* shut up bandit

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* clean up a bit

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-18 15:29:10 +01:00
Jens L.
62dc04a684 stages: remove more global state (#18641)
* add auth for active flow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* migrate duo

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* migrate sms

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* migrate consent

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* migrate email and fix broken tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fallback

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* merge flow plan when restoring from token

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-18 13:12:35 +01:00
dependabot[bot]
68f3bf6ec1 web: bump the storybook group across 1 directory with 5 updates (#18817)
Bumps the storybook group with 4 updates in the /web directory: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links), [@storybook/web-components](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/web-components) and [@storybook/web-components-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/web-components-vite).


Updates `@storybook/addon-docs` from 10.1.7 to 10.1.8
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.8/code/addons/docs)

Updates `@storybook/addon-links` from 10.1.7 to 10.1.8
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.8/code/addons/links)

Updates `@storybook/web-components` from 10.1.7 to 10.1.8
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.8/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.1.7 to 10.1.8
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.8/code/frameworks/web-components-vite)

Updates `storybook` from 10.1.7 to 10.1.8
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.8/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 13:02:01 +01:00
dependabot[bot]
8234613b76 web: bump the swc group across 1 directory with 11 updates (#18923)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc).


Updates `@swc/core` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-darwin-arm64` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-darwin-x64` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-linux-arm-gnueabihf` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-linux-arm64-gnu` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-linux-arm64-musl` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-linux-x64-gnu` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-linux-x64-musl` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-win32-arm64-msvc` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-win32-ia32-msvc` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

Updates `@swc/core-win32-x64-msvc` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.5...v1.15.6)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.15.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 13:01:49 +01:00
dependabot[bot]
eec998cc8d lifecycle/aws: bump aws-cdk from 2.1100.0 to 2.1100.1 in /lifecycle/aws (#18922)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1100.0 to 2.1100.1.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1100.1/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1100.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 13:00:30 +01:00
authentik-automation[bot]
d01aa6bebf core, web: update translations (#18920)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-18 12:57:02 +01:00
dependabot[bot]
cbbf315662 web: bump knip from 5.74.0 to 5.75.1 in /web (#18924)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.74.0 to 5.75.1.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/5.75.1/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.75.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 12:55:58 +01:00
dependabot[bot]
45ca767fd8 web: bump chromedriver from 143.0.1 to 143.0.2 in /web (#18926)
Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 143.0.1 to 143.0.2.
- [Commits](https://github.com/giggio/node-chromedriver/compare/143.0.1...143.0.2)

---
updated-dependencies:
- dependency-name: chromedriver
  dependency-version: 143.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 12:55:32 +01:00
dependabot[bot]
5d3e2e89e0 core: bump goauthentik/fips-debian from 189345a to 10dadf1 (#18927)
Bumps goauthentik/fips-debian from `189345a` to `10dadf1`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-debian
  dependency-version: trixie-slim-fips
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 12:55:06 +01:00
Marcelo Elizeche Landó
5e2f261a0c tests/e2e: handle StaleElementReferenceException in parse_json_content (#18842)
Add retry logic and safer text recover
2025-12-17 20:27:03 -03:00
Tana M Berry
10a421e678 web/admin: add UI copy to RBAC modal (#18917)
add UI copy to RBAC modal
2025-12-17 20:23:20 +00:00
Marc 'risson' Schmitt
668ad3dadf root: fix docker-compose data mount (#18903) 2025-12-17 16:33:50 -03:00
João C. Fernandes
e7903d5391 core/groups: optimize prefetch queries to fetch only required fields (#18448)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-17 17:15:31 +00:00
Jens L.
e38fffc44c web/admin: reword some things on the device view page (#18785)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-17 17:33:36 +01:00
Marc 'risson' Schmitt
4bc2bca448 website/docs: 2025.12: remove superfluous changes (#18910) 2025-12-17 16:24:12 +00:00
Jens L.
48916303d8 api: fix latest version for public schema (#18902) 2025-12-17 15:12:14 +00:00
Marc 'risson' Schmitt
d28109da6a ci/release-tag: checkout correct branch for make test-docker (#18880) 2025-12-17 15:08:05 +00:00
Marc 'risson' Schmitt
3bd299d52a api: fix page_size with invalid query param (#18879) 2025-12-17 15:06:22 +00:00
Tana M Berry
57418582c5 website/docs: added list of Int Guide contributors (also edited frontmatter) (#18888)
* alphabetize

* added frontmatter

* Apply suggestions from code review

Signed-off-by: Dominic R <dominic@sdko.org>

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-17 09:01:05 -06:00
Marc 'risson' Schmitt
f37958bcd0 tasks/middleware: close connections on worker status update database error (#18881) 2025-12-17 15:45:37 +01:00
dependabot[bot]
8931b621b4 core: bump goauthentik.io/api/v3 from 3.2026020.1 to 3.2026020.3 (#18892)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 13:24:57 +00:00
dependabot[bot]
9d3d96bab1 web: bump the eslint group across 1 directory with 5 updates (#18851)
Bumps the eslint group with 5 updates in the /web directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.1` | `9.39.2` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.49.0` | `8.50.0` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.49.0` | `8.50.0` |
| [eslint](https://github.com/eslint/eslint) | `9.39.1` | `9.39.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.49.0` | `8.50.0` |



Updates `@eslint/js` from 9.39.1 to 9.39.2
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.2/packages/js)

Updates `@typescript-eslint/eslint-plugin` from 8.49.0 to 8.50.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.50.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.49.0 to 8.50.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.50.0/packages/parser)

Updates `eslint` from 9.39.1 to 9.39.2
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.1...v9.39.2)

Updates `typescript-eslint` from 8.49.0 to 8.50.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.50.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: eslint
  dependency-version: 9.39.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 13:15:56 +01:00
dependabot[bot]
712f0ed95e web: bump the swc group across 1 directory with 11 updates (#18818)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc).


Updates `@swc/core` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-darwin-arm64` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-darwin-x64` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-linux-arm-gnueabihf` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-linux-arm64-gnu` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-linux-arm64-musl` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-linux-x64-gnu` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-linux-x64-musl` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-win32-arm64-msvc` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-win32-ia32-msvc` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

Updates `@swc/core-win32-x64-msvc` from 1.15.3 to 1.15.4
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.15.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:32:37 +01:00
dependabot[bot]
1cd9c7bf9d web: bump the goauthentik group across 1 directory with 3 updates (#18819)
Bumps the goauthentik group with 3 updates in the /web directory: [@goauthentik/esbuild-plugin-live-reload](https://github.com/goauthentik/authentik/tree/HEAD/packages/esbuild-plugin-live-reload), [@goauthentik/eslint-config](https://github.com/goauthentik/authentik/tree/HEAD/packages/eslint-config) and [@goauthentik/prettier-config](https://github.com/goauthentik/authentik/tree/HEAD/packages/prettier-config).


Updates `@goauthentik/esbuild-plugin-live-reload` from 1.3.1 to 1.4.0
- [Release notes](https://github.com/goauthentik/authentik/releases)
- [Commits](https://github.com/goauthentik/authentik/commits/HEAD/packages/esbuild-plugin-live-reload)

Updates `@goauthentik/eslint-config` from 1.1.1 to 1.2.0
- [Release notes](https://github.com/goauthentik/authentik/releases)
- [Commits](https://github.com/goauthentik/authentik/commits/HEAD/packages/eslint-config)

Updates `@goauthentik/prettier-config` from 3.2.1 to 3.3.1
- [Release notes](https://github.com/goauthentik/authentik/releases)
- [Commits](https://github.com/goauthentik/authentik/commits/HEAD/packages/prettier-config)

---
updated-dependencies:
- dependency-name: "@goauthentik/esbuild-plugin-live-reload"
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: goauthentik
- dependency-name: "@goauthentik/eslint-config"
  dependency-version: 1.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: goauthentik
- dependency-name: "@goauthentik/prettier-config"
  dependency-version: 3.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: goauthentik
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:32:22 +01:00
dependabot[bot]
fb23751079 lifecycle/aws: bump aws-cdk from 2.1034.0 to 2.1100.0 in /lifecycle/aws (#18850)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1034.0 to 2.1100.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1100.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1100.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:32:01 +01:00
dependabot[bot]
e49aace000 web: bump @sentry/browser from 10.30.0 to 10.31.0 in /web in the sentry group across 1 directory (#18893)
web: bump @sentry/browser in /web in the sentry group across 1 directory

Bumps the sentry group with 1 update in the /web directory: [@sentry/browser](https://github.com/getsentry/sentry-javascript).


Updates `@sentry/browser` from 10.30.0 to 10.31.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.30.0...10.31.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 10.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:31:27 +01:00
dependabot[bot]
876b299f30 web: bump the bundler group across 1 directory with 7 updates (#18894)
Bumps the bundler group with 2 updates in the /web directory: [@vitest/browser](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser) and [esbuild](https://github.com/evanw/esbuild).


Updates `@vitest/browser` from 4.0.15 to 4.0.16
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/browser)

Updates `@vitest/browser-playwright` from 4.0.15 to 4.0.16
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/browser-playwright)

Updates `esbuild` from 0.27.1 to 0.27.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.1...v0.27.2)

Updates `vitest` from 4.0.15 to 4.0.16
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/vitest)

Updates `@esbuild/darwin-arm64` from 0.27.1 to 0.27.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.1...v0.27.2)

Updates `@esbuild/linux-arm64` from 0.27.1 to 0.27.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.1...v0.27.2)

Updates `@esbuild/linux-x64` from 0.27.1 to 0.27.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.1...v0.27.2)

---
updated-dependencies:
- dependency-name: "@vitest/browser"
  dependency-version: 4.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: esbuild
  dependency-version: 0.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: vitest
  dependency-version: 4.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: "@esbuild/darwin-arm64"
  dependency-version: 0.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: "@esbuild/linux-arm64"
  dependency-version: 0.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: "@esbuild/linux-x64"
  dependency-version: 0.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:31:15 +01:00
dependabot[bot]
458439c396 web: bump the rollup group across 1 directory with 4 updates (#18852)
Bumps the rollup group with 4 updates in the /web directory: [@rollup/rollup-darwin-arm64](https://github.com/rollup/rollup), [@rollup/rollup-linux-arm64-gnu](https://github.com/rollup/rollup), [@rollup/rollup-linux-x64-gnu](https://github.com/rollup/rollup) and [rollup](https://github.com/rollup/rollup).


Updates `@rollup/rollup-darwin-arm64` from 4.53.3 to 4.53.4
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.3...v4.53.4)

Updates `@rollup/rollup-linux-arm64-gnu` from 4.53.3 to 4.53.4
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.3...v4.53.4)

Updates `@rollup/rollup-linux-x64-gnu` from 4.53.3 to 4.53.4
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.3...v4.53.4)

Updates `rollup` from 4.53.3 to 4.53.4
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.53.3...v4.53.4)

---
updated-dependencies:
- dependency-name: "@rollup/rollup-darwin-arm64"
  dependency-version: 4.53.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-arm64-gnu"
  dependency-version: 4.53.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-version: 4.53.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: rollup
  dependency-version: 4.53.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:29:44 +01:00
dependabot[bot]
d3d0effe9d web: bump @types/node from 25.0.0 to 25.0.3 in /web (#18895)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.0.0 to 25.0.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:29:30 +01:00
dependabot[bot]
413b073191 web: bump knip from 5.73.3 to 5.74.0 in /web (#18896)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.73.3 to 5.74.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/5.74.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.74.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:28:48 +01:00
dependabot[bot]
46747ae3f2 core: bump goauthentik/fips-debian from 2f19fc1 to 189345a (#18897)
Bumps goauthentik/fips-debian from `2f19fc1` to `189345a`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-debian
  dependency-version: trixie-slim-fips
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:28:39 +01:00
dependabot[bot]
d64a3aab39 core: bump astral-sh/uv from 0.9.17 to 0.9.18 (#18898)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.9.17 to 0.9.18.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.9.17...0.9.18)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.9.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 12:28:28 +01:00
Dominic R
970cddae47 website/integrations: bookstack: fix redir url (#18891) 2025-12-17 08:42:50 +00:00
authentik-automation[bot]
24c4495ac2 core, web: update translations (#18807)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-17 00:42:03 +00:00
Dewi Roberts
ff38607fa3 web/admin: endpoint: change wording and add helper text (#18871)
* Change wording and add helper text

* Fix helper text

* Setup text change

* Update web/src/admin/endpoints/connectors/agent/AgentConnectorSetup.ts

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update web/src/admin/endpoints/connectors/agent/AgentConnectorSetup.ts

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>

* Update web/src/admin/endpoints/connectors/agent/EnrollmentTokenForm.ts

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>

* Format.

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Teffen Ellis <teffen@sister.software>
2025-12-16 23:28:29 +00:00
Dominic R
eef8e57f6c web: fix file upload form (#18808)
* web: fix file upload form name mismatch and modal submit promise handling

Fixes the following error:

FileUploadForm.ts:74  POST http://authentik.localhost:9000/api/v3/admin/file/ 405 (Method Not Allowed)
(anonymous) @ fetch.ts:81
fetchApi @ runtime.ts:206
await in fetchApi
request @ runtime.ts:136
await in request
adminFileCreateRaw @ AdminApi.ts:191
adminFileCreate @ AdminApi.ts:206
send @ FileUploadForm.ts:74
submit @ Form.ts:363
(anonymous) @ ModalForm.ts:54
handleEvent @ lit-html.ts:2109
n @ helpers.ts:117Understand this error
Form.ts:403 authentik/forms: API rejected the form submission due to an invalid field that doesn't appear to be in the form. This is likely a bug in authentik. {detail: 'Response returned an error code'}
(anonymous) @ console.ts:39
(anonymous) @ Form.ts:403
Promise.catch
submit @ Form.ts:376
(anonymous) @ ModalForm.ts:54
handleEvent @ lit-html.ts:2109
n @ helpers.ts:117Understand this error
runtime.ts:140 Uncaught (in promise) ResponseError: Response returned an error code
    at mR.request (runtime.ts:140:15)
    at async mR.adminFileCreateRaw (AdminApi.ts:191:26)
    at async mR.adminFileCreate (AdminApi.ts:206:9)

- align file upload rename field with api name so validation errors map correctly
-improve custom filename extension logic to avoid double or incorrect  extensions
- prevent unhandled promise rejections from modal submit click handler and show  missing-form errors to users

* rev

* wip

* Update ModalForm.ts

Signed-off-by: Dominic R <dominic@sdko.org>

* scope better

* fix what it validates against

---------

Signed-off-by: Dominic R <dominic@sdko.org>
2025-12-16 18:37:22 +00:00
Jens L.
603820854b stages/authenticator_*: fix code input field not string (#18875)
* stages/authenticator_*: fix code input field not string

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Update authentik/stages/authenticator_totp/stage.py

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens L. <jens@beryju.org>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-16 19:10:48 +01:00
dependabot[bot]
4ad7f8be2a web: bump vite from 7.2.7 to 7.3.0 in /web (#18854)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.2.7 to 7.3.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.0/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 18:44:54 +01:00
Alexander Tereshkin
a605cd1e87 web: fix notification counter (#18781) 2025-12-16 18:41:11 +01:00
Alexander Tereshkin
936789f534 web: add custom message with links for empty data export list (#18830) 2025-12-16 17:36:12 +01:00
Jens L.
2f52d832ab website/docs: 2025.10.3 release notes (#18868)
* website/docs: 2025.10.3 release notes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-16 16:56:57 +01:00
Marcelo Elizeche Landó
036514730e website/docs: Add docs for passkey autofill (WebauthN Conditional UI) (#18805)
* Add docs for passkey autofill feature

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>

* improve configuration section

* remove blank lines

---------

Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-16 15:16:12 +00:00
Simonyi Gergő
d48129ba7b website/docs: adjust RBAC-related details in 2025.12 release notes (#18863)
* website/docs: adjust RBAC-related details in 2025.12 release notes

* adjust wording

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>

---------

Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-12-16 15:02:00 +00:00
Jens L.
d219f72ed6 outposts: fix permission errors for related certificates (#18861)
* outposts: fix permission errors for related certificates

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-16 15:01:25 +01:00
Simonyi Gergő
7b19045431 web/admin/rbac: misc object permission fixes (#18859)
* rbac: relabel "Assign role permissions" button

* fix object permissions in permissions table

This should have a backend-based fix in the future.
2025-12-16 14:33:25 +01:00
dependabot[bot]
0027813e4b core: bump library/golang from 5d35fb8 to 8e8f9c8 (#18855)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 13:09:10 +00:00
Simonyi Gergő
a6ebf1074f rbac: alter migrated direct permission roles (#18860)
These should not be marked as managed, because many of these permissions
were created by admins.
2025-12-16 13:40:49 +01:00
Marcelo Elizeche Landó
ea9689c493 core: add skip s3_test_server_available to TestResolveFileUrlS3Backend (#18858)
add skip s3_test_server_available to TestResolveFileUrlS3Backend
2025-12-16 12:59:13 +01:00
Jens L.
06e7335618 ci: replace codecov test-results action (#18862)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-16 12:50:54 +01:00
dependabot[bot]
42c4fee053 core: bump goauthentik/fips-debian from c10cd2c to 2f19fc1 (#18856)
Bumps goauthentik/fips-debian from `c10cd2c` to `2f19fc1`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-debian
  dependency-version: trixie-slim-fips
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 12:33:13 +01:00
Dominic R
26cfbe67f3 admin/files: fix get_objects_for_user queryset argument in FileUsedByView (#18845)
Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2025-12-16 00:39:13 +00:00
Marcelo Elizeche Landó
2a17024afc core: skip s3 tests if endpoint isn't available (#18841)
skip s3 tests if endpoint isn't available
2025-12-15 20:22:59 -03:00
Connor Peshek
c557b55e0e crypto: Store details parsed from includeDetails in database instead (#18013)
* crypto: Store details parsed from includeDetails in database instead

* fix signal for tests

* Update authentik/crypto/signals.py

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Connor Peshek <connor@connorpeshek.me>

* Update authentik/crypto/apps.py

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Connor Peshek <connor@connorpeshek.me>

* Update authentik/crypto/signals.py

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Connor Peshek <connor@connorpeshek.me>

* Add feedback

* cleanup

* update

* cleanup

* simplify serializer

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Update KID for when updating certificates

* lint

---------

Signed-off-by: Connor Peshek <connor@connorpeshek.me>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: connor peshek <connorpeshek@connors-MacBook-Pro.local>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-15 13:50:16 -06:00
Roi Gabay
f56e354e38 website/docs: add jellyseer integration doc (#18812)
* website/docs: add jellyseer integration doc

* Slight tweaks

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/media/jellyseerr/index.md

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-15 17:12:06 +00:00
Marc 'risson' Schmitt
c50c2b0e0c admin/files: revert add check for /media existence (#18636) (#18829) 2025-12-15 15:29:21 +00:00
dependabot[bot]
662124cac9 core: bump goauthentik.io/api/v3 from 3.2025120.26 to 3.2026020.1 (#18815)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 15:20:48 +00:00
Marc 'risson' Schmitt
3d671a901b packages/django-dramatiq-postgres: broker: close django connections on consumer close (#18833)
Co-authored-by: Norman Ziebal <norman.ziebal@mail.schwarz>
2025-12-15 14:59:51 +01:00
Simonyi Gergő
a7fb031b64 core: remove superuser check from Token list (#18684) 2025-12-15 14:29:42 +01:00
Dewi Roberts
2818b0bbdf website/docs: add icon info to style guide (#18832) 2025-12-15 13:27:22 +00:00
Ryan Pesek
60075e39fb core: list applications fix (#18798) 2025-12-15 13:16:07 +01:00
dependabot[bot]
c112f702b3 ci: bump actions/cache from 5.0.0 to 5.0.1 (#18823)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 13:15:05 +01:00
dependabot[bot]
42b3323b3d ci: bump actions/download-artifact from 6.0.0 to 7.0.0 (#18825)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 13:14:22 +01:00
dependabot[bot]
78380831de core: bump goauthentik/fips-debian from 07f41ce to c10cd2c (#18822)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 13:14:09 +01:00
dependabot[bot]
8b5195aeff ci: bump actions/upload-artifact from 5.0.0 to 6.0.0 (#18824)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](330a01c490...b7c566a772)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 12:45:16 +01:00
dependabot[bot]
d762e38027 ci: bump astral-sh/setup-uv from 7.1.5 to 7.1.6 in /.github/actions/setup (#18826)
ci: bump astral-sh/setup-uv in /.github/actions/setup

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.5 to 7.1.6.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](ed21f2f24f...681c641aba)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 7.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 12:36:47 +01:00
Marcelo Elizeche Landó
e427cb611e root: Add macOS support for sed in Makefile (#18795)
Add macOS support for sed
2025-12-15 12:09:35 +01:00
authentik-automation[bot]
20dbcf2e7b core, web: update translations (#18804)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-14 00:55:55 +01:00
dependabot[bot]
d93138f790 lifecycle/aws: bump aws-cdk from 2.1033.0 to 2.1034.0 in /lifecycle/aws (#18771)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 00:53:47 +01:00
Jens L.
9ef7f706e9 internal: don't warn on empty outpost for embedded (#18786)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-14 00:50:58 +01:00
dependabot[bot]
627176ab7e web: bump the storybook group across 1 directory with 5 updates (#18774)
Bumps the storybook group with 4 updates in the /web directory: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links), [@storybook/web-components](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/web-components) and [@storybook/web-components-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/web-components-vite).


Updates `@storybook/addon-docs` from 10.1.6 to 10.1.7
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.7/code/addons/docs)

Updates `@storybook/addon-links` from 10.1.6 to 10.1.7
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.7/code/addons/links)

Updates `@storybook/web-components` from 10.1.6 to 10.1.7
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.7/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.1.6 to 10.1.7
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.7/code/frameworks/web-components-vite)

Updates `storybook` from 10.1.6 to 10.1.7
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.7/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 11:22:11 +01:00
dependabot[bot]
069622aea4 web: bump chromedriver from 143.0.0 to 143.0.1 in /web (#18776)
Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 143.0.0 to 143.0.1.
- [Commits](https://github.com/giggio/node-chromedriver/compare/143.0.0...143.0.1)

---
updated-dependencies:
- dependency-name: chromedriver
  dependency-version: 143.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 11:21:43 +01:00
dependabot[bot]
3da523cbd5 web: bump the react group across 1 directory with 2 updates (#18775)
Bumps the react group with 2 updates in the /web directory: [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom).


Updates `react` from 19.2.1 to 19.2.3
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.3/packages/react)

Updates `react-dom` from 19.2.1 to 19.2.3
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.3/packages/react-dom)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: react-dom
  dependency-version: 19.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 11:18:06 +01:00
Dominic R
126310138d web/admin: fix read-only provider selection for application form (#18768)
web/admin: Add read-only provider selection for application form

One of the ways to "bind" an application to a provider is to click "Create" under "Assigned to application" in the provider view. This PR fixes 2 issues:

* The value is now auto-filled, so the user doesn't need to do that anymore
* The value is now read-only, you don't need to change it since it's well for that provider. If that makes sense
2025-12-12 22:40:55 +00:00
Marcelo Elizeche Landó
9f1e55fbe6 website/release notes: Update v2025.12 release notes (#18797)
* website/release notes: Update v2025.12 release notes

Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>

* fix linting

---------

Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2025-12-12 22:25:10 +00:00
Marcelo Elizeche Landó
5997cda48b tests/e2e: retry detached shadow roots (#18796)
tests(e2e): retry detached shadow roots
2025-12-12 22:09:15 +00:00
authentik-automation[bot]
fbe8028b08 root: bump version to 2026.2.0-rc1 (#18794)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-12 20:59:47 +00:00
Marcelo Elizeche Landó
c0eff71873 website/release notes: Release notes for v2025.12 (#18595)
* First draft of release notes for v2025.12

Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* add many words

* fix linting

* add content to highlights

* glossary

* tweak

* Add passkey autofill details

* fix linting

* improve descriptions for CSV Data Exports, Files, UI improvements, Promoted Source and localization

* minor fixes

* remove links

---------

Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-12-12 16:56:58 -03:00
authentik-automation[bot]
7b9c44b004 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#18793)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-12 19:41:48 +00:00
Ken Sternberg
62f1de5993 web/admin: make empty table message configurable (#18763)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

- Bugfix: adds the InvalidationFlow to the Radius Provider dialogues
  - Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated
    to the Notification.
- Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/`

## Note

Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.

* This (temporary) change is needed to prevent the unit tests from failing.

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

* Revert "This (temporary) change is needed to prevent the unit tests from failing."

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

Fixes bad escaping of URLs in the release notes that resulted in mangled output.

v2024.6.4 had entries that looked like this:

```
##### `GET` /providers/google_workspace/{#123;id}#125;/
```

v2025.4.md had entries that looked like this:

```
##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/
```

A couple of straightforward search-and-replaces has fixed the issue.

## Notes

Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past,
but it was obvious when visiting the page.

@Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while
Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd
behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues
in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"),
which could be very confusing.

The inconsistencies between the two releases, and the working releases, suggests that the error was
introduced manually.

* admin: make empty table message configurable

# What

This commit provides a new field at the Table level for the empty state message. The field defaults to the original message, “No objects found.”

# Why

The icon has long been configurable, but not the message. It makes sense to customize this message and let people know if they’re looking at files, properties, applications, and other objects.
2025-12-12 19:33:42 +00:00
Teffen Ellis
17489fa695 web: Fix background refreshing too frequently. (#18764) 2025-12-12 13:34:20 -05:00
Teffen Ellis
94ae8b7b80 web: Fix switch labels (#18741)
* Fix switch alignment:

* Fix ARIA.
2025-12-12 18:25:17 +01:00
Ryan Pesek
69b98fcbac core: optimize list applications (#18330)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-12 17:35:24 +01:00
authentik-automation[bot]
d09c7098de core, web: update translations (#18766)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-12 16:22:04 +00:00
Marc 'risson' Schmitt
bba0aed68f web/admin: fix typo in PolicyAccessView (#18789) 2025-12-12 16:08:57 +00:00
transifex-integration[bot]
3ae5d717cd translate: Updates for project authentik and language fr_FR (#18788)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-12 15:14:35 +00:00
dependabot[bot]
c5d69ec020 core: bump goauthentik/fips-debian from dea09c4 to 07f41ce (#18778)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 15:13:32 +00:00
Marc 'risson' Schmitt
ae019ebe04 admin/files: cache expensive generated URLs (#18784) 2025-12-12 13:41:42 +00:00
dependabot[bot]
7484b153ac ci: bump actions/cache from 4.3.0 to 5.0.0 (#18779)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:30:11 +00:00
dependabot[bot]
acc7c02105 ci: bump tj-actions/changed-files from 47.0.0 to 47.0.1 (#18780)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:28:58 +00:00
dependabot[bot]
80ed53000d core: bump goauthentik.io/api/v3 from 3.2025120.25 to 3.2025120.26 (#18770)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:28:09 +00:00
dependabot[bot]
d90a41a186 core: bump selenium/standalone-chromium from 142.0 to 143.0 in /tests/e2e (#18772)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:27:55 +00:00
dependabot[bot]
55ab2f13d6 web: bump types (merge branch) (#18735)
* web: bump @types/node from 24.10.3 to 25.0.0 in /web/packages/core

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.3 to 25.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump packages.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Teffen Ellis <teffen@sister.software>
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-12-12 08:22:34 -05:00
Marc 'risson' Schmitt
7f9961981f ci: generate typescript api client for release too (#18761) 2025-12-12 14:04:28 +01:00
Sirofwalls
cafe9e3808 website/integrations: wazuh: Change exchange key generation to 64 bytes (#18769)
Change exchange key generation to 64 bytes

Updated the command to generate an exchange key from 32 to 64 bytes. as of wazuh 4.9 the exchange key needs to be 64 characters long

Signed-off-by: Sirofwalls <58705178+sirofwalls@users.noreply.github.com>
2025-12-12 09:39:08 +00:00
Alexander Tereshkin
3d9632c8a5 website/docs: fix incorrect menu reference in data exports doc (#18752)
enterprise/reports: fix incorrect menu reference in data exports doc
2025-12-11 18:04:46 +00:00
transifex-integration[bot]
895a2fdd4a translate: Updates for project authentik and language zh-Hans (#18756)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 18:03:30 +00:00
transifex-integration[bot]
a94035ddd6 translate: Updates for project authentik and language tr_TR (#18758)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:53:42 +00:00
transifex-integration[bot]
f042056c5c translate: Updates for project authentik and language fi_FI (#18759)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:52:29 +00:00
transifex-integration[bot]
91965146b5 translate: Updates for project authentik and language pl_PL (#18754)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:42:38 +00:00
transifex-integration[bot]
25a45e0f9f translate: Updates for project authentik and language ru_RU (#18745)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:42:03 +00:00
transifex-integration[bot]
e0ec797f58 translate: Updates for project authentik and language ko_KR (#18760)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:40:59 +00:00
transifex-integration[bot]
61377e9b13 translate: Updates for project authentik and language ja_JP (#18755)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:40:20 +00:00
transifex-integration[bot]
a225d68f52 translate: Updates for project authentik and language de_DE (#18749)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:39:43 +00:00
transifex-integration[bot]
0afe14a52f translate: Updates for project authentik and language nl_NL (#18751)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:38:07 +00:00
transifex-integration[bot]
2442759fc2 translate: Updates for project authentik and language pt_BR (#18746)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:35:04 +00:00
transifex-integration[bot]
0c19d1ec61 translate: Updates for project authentik and language es_ES (#18748)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:31:13 +00:00
transifex-integration[bot]
1bda55de9f translate: Updates for project authentik and language it_IT (#18750)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:26:30 +00:00
transifex-integration[bot]
da975c3086 translate: Updates for project authentik and language cs_CZ (#18753)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:14:54 +00:00
transifex-integration[bot]
37937422ce translate: Updates for project authentik and language fr_FR (#18747)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-11 17:13:15 +00:00
Marcelo Elizeche Landó
15b93a5e9d stages/identification: Add WebAuthn conditional UI (passkey autofill) support (#18377)
* add passkey_login to identification stage

* handle passkey auth in identification stage

* Add passkey settings in identification stage in the admin UI

* Add UI changes for basic passkey conditional login

* Fix linting

* rework

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update tests

* update admin form

* allow passing stage to validate_challenge_webauthn

* update flows/tests/test_inspector.py

* update for new field

* Fix linting

* update go solvers for identification challenge

* Refactor tests

* Skip mfa validation if user already authenticated via passkey at identification stage

* Add skip_if_passkey_authenticated option to authenticator validate stage and UI

* Add e2e test for passkey login conditional ui

* add policy

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Remove skip_if_passkey_authenticated

* fix blueprint

* Set backend so password stage policy knows user is already authenticated

* Set backend so password stage policy knows user is already authenticated

* fix linting

* slight tweaks

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* simplify e2e test

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-11 11:49:05 -03:00
Nuno Alves
196bce348f api: allow configuring default page_size and max_page_size (#18165)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-11 14:45:50 +00:00
Marc 'risson' Schmitt
a0c33233d5 root: do not require backend approval for npm workspace dependencies (#18738) 2025-12-11 14:45:34 +00:00
Dominic R
3353db0d7f outpost/proxyv2: more tests, fix pg password with spaces, and existing session on restart (#18211)
* outpost/proxyv2: handle PostgreSQL passwords with spaces and special characters

And modify / add some more tests and a bit of refactoring

* Potential fix for code scanning alert no. 268: Disabled TLS certificate check

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: Dominic R <dominic@sdko.org>

* Revert "Potential fix for code scanning alert no. 268: Disabled TLS certificate check"

This reverts commit ead227a272.

* wip

* fix incorrect status code in error response

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-11 14:25:41 +00:00
dependabot[bot]
d1a3f76188 web: bump @types/guacamole-common-js from 1.5.4 to 1.5.5 in /web (#18717)
Bumps [@types/guacamole-common-js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/guacamole-common-js) from 1.5.4 to 1.5.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/guacamole-common-js)

---
updated-dependencies:
- dependency-name: "@types/guacamole-common-js"
  dependency-version: 1.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 14:42:36 +01:00
Hosh
224eb938c2 lib: do not strip and re-add curly braces from raw JSON config (#13769)
* Do not strip and re-add curly braces from JSON

Signed-off-by: Hosh <hoshsadiq@users.noreply.github.com>

* Fix tests

---------

Signed-off-by: Hosh <hoshsadiq@users.noreply.github.com>
Co-authored-by: Hosh <hoshsadiq@users.noreply.github.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-11 13:29:48 +01:00
dependabot[bot]
49fafa1e7c core: bump library/nginx from 325b00a to fb01117 in /website (#18737)
Bumps library/nginx from `325b00a` to `fb01117`.

---
updated-dependencies:
- dependency-name: library/nginx
  dependency-version: 1.29-trixie
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 12:51:18 +01:00
dependabot[bot]
6f1c486dca core: bump goauthentik.io/api/v3 from 3.2025120.21 to 3.2025120.25 (#18732)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025120.21 to 3.2025120.25.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2025120.21...v3.2025120.25)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025120.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 12:50:10 +01:00
dependabot[bot]
15c56aa47f web: bump the storybook group across 1 directory with 5 updates (#18715)
Bumps the storybook group with 4 updates in the /web directory: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links), [@storybook/web-components](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/web-components) and [@storybook/web-components-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/web-components-vite).


Updates `@storybook/addon-docs` from 10.1.4 to 10.1.5
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.5/code/addons/docs)

Updates `@storybook/addon-links` from 10.1.4 to 10.1.5
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.5/code/addons/links)

Updates `@storybook/web-components` from 10.1.4 to 10.1.5
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.5/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.1.4 to 10.1.5
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.5/code/frameworks/web-components-vite)

Updates `storybook` from 10.1.4 to 10.1.5
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.1.5/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 12:37:56 +01:00
dependabot[bot]
b7502d0485 web: bump knip from 5.72.0 to 5.73.1 in /web (#18734)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.72.0 to 5.73.1.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/5.73.1/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.73.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 12:37:36 +01:00
dependabot[bot]
882fd0966c core: bump goauthentik/fips-debian from e72277d to dea09c4 (#18736)
Bumps goauthentik/fips-debian from `e72277d` to `dea09c4`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-debian
  dependency-version: trixie-slim-fips
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 12:34:15 +01:00
Tyler
ef6a64076c website/integrations: m365: fix string match (#18731)
Minor doc error: M365 SAML w/o AD Source

This needs to just be a simple string match, and earlier in the docs the custom property mapping is created that simply returns "user.email"

Signed-off-by: Tyler <tyler@fenby.tech>
2025-12-10 23:57:21 -05:00
authentik-automation[bot]
a1e6b086cd core, web: update translations (#18730)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-10 23:33:53 +00:00
Teffen Ellis
2a2da34eab web: Locale selector (#18560)
* web: Locale selector

* Fix label, hover state.

* Persist locale to session. Fix stale render. Update middleware.

* Fix background color.
2025-12-10 15:51:17 -05:00
Alexander Tereshkin
572d965084 sources/telegram: implement connecting existing user to a Telegram account (#18517) 2025-12-10 18:20:40 +01:00
Marc 'risson' Schmitt
92c5efbac1 sources/sync: configuration for outgoing sync trigger mode (#17669)
* sources/sync: configuration for outgoing sync trigger mode

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* api and frontend

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* update migrations

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Wrap `msg` calls in function to fix translation. Update props to accept
callbacks.

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-12-10 12:40:32 -03:00
Dominic R
b4b89e9633 website/integrations: add KitchenOwl (#18687)
* website/integrations: add KitchenOwl OIDC integration documentation

* wip

* Small changes

---------

Co-authored-by: Victor Löfgren <victor.lofgren99@pm.me>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
2025-12-10 15:27:10 +00:00
Marc 'risson' Schmitt
54be51862a admin/files: add check for /media existence (#18636) 2025-12-10 11:56:45 -03:00
Marc 'risson' Schmitt
03a2212657 lifecycle/migrate: remove tenant_files migration (#18729) 2025-12-10 14:03:17 +00:00
dependabot[bot]
a50936f2e7 core: bump goauthentik.io/api/v3 from 3.2025120.19 to 3.2025120.21 (#18714)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 13:31:14 +00:00
dependabot[bot]
ae44cb0ca2 core: bump library/golang from b669435 to 5d35fb8 (#18718)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 13:29:13 +00:00
dependabot[bot]
f0132570ca ci: bump codecov/codecov-action from 5.5.1 to 5.5.2 in /.github/actions/test-results (#18722)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 13:28:09 +00:00
dependabot[bot]
6a922a63d8 ci: bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#18721)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 13:27:45 +00:00
authentik-automation[bot]
efa35ba94b core, web: update translations (#18713)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-10 14:10:18 +01:00
dependabot[bot]
6763636242 core: bump goauthentik/fips-debian from cb2d1f8 to e72277d (#18720)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 14:09:00 +01:00
dependabot[bot]
d78ae5c55e core: bump library/nginx from e21f8d0 to 325b00a in /website (#18724)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 14:07:54 +01:00
dependabot[bot]
ca714d819c core: bump library/node from 7942b33 to ccfd9da in /website (#18725)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 14:07:46 +01:00
Connor Peshek
efdc11e413 web/admin: Add SAML metadata form to wizard (#17690)
Co-authored-by: connor peshek <connorpeshek@connors-MacBook-Pro.local>
2025-12-10 13:58:13 +01:00
Anduin Xue
cd09bff247 sources/oauth: add WeChat type (#18086)
* Add wechat.

* Refactor comments and formatting in wechat.py

Signed-off-by: Anduin Xue <anduin@aiursoft.com>

* Fix lint.

Signed-off-by: Anduin Xue <anduin@aiursoft.com>

* Fix lint.

* fix: Rename `WeChat` enum member to `Wechat` for consistency

* docs: Add WeChat social login integration guide.

* Docs updates

* Revise WeChat integration instructions

Updated instructions for creating a WeChat Website Application and added details about scopes and user attribute mappings.

Signed-off-by: Anduin Xue <anduin@aiursoft.com>

* Prettier

* Update wechat.py

Signed-off-by: Anduin Xue <anduin@aiursoft.com>

---------

Signed-off-by: Anduin Xue <anduin@aiursoft.com>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-10 12:48:12 +00:00
Simonyi Gergő
4c07b7ae81 blueprints: remove pk from recovery example (#18712) 2025-12-10 13:15:09 +01:00
dependabot[bot]
320a6ce137 core: bump astral-sh/uv from 0.9.16 to 0.9.17 (#18723)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.9.16 to 0.9.17.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.9.16...0.9.17)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.9.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 12:32:53 +01:00
Teffen Ellis
1f21d2e8e6 web: 2025.12 UI tidy (#18650)
* Fix box shadow, scrollbars.

* Fix contrast.

* Fix field alignment.

* Fix class ordering.

* Fix button colors while in nested table.

* Fix background color on light mode.

* Fix chip colors, spacing.

* Fix overlap of switch during transition.
2025-12-10 02:15:14 +01:00
Marcelo Elizeche Landó
d113204872 packages/ak-guardian: bump python requirement to 3.14 (#18711)
packages/ak-guardian: bump python to 3.14

Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2025-12-09 23:21:04 +00:00
Dominic R
d1c2c1c565 contributing: don't use main branch (#18688)
* contributing: don't use main branch

* lint

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

---------

Signed-off-by: Dominic R <dominic@sdko.org>
2025-12-09 23:12:17 +00:00
Jens L.
379a9d09f1 endpoints: fix device access group missing from blueprint (#18703)
* endpoints: fix device access group missing from blueprint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* also fix flow_set not being read_only

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix general blueprint schema issue of incorrect related PK fields having the wrong type some places

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-09 19:46:49 +01:00
Marc 'risson' Schmitt
68d0b02e00 tenants: remove extra query for each request (#18705) 2025-12-09 17:52:52 +00:00
dependabot[bot]
4d289ecb75 web: bump dompurify from 3.3.0 to 3.3.1 in /web (#18694)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.0 to 3.3.1.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.3.0...3.3.1)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 12:51:27 -05:00
dependabot[bot]
e6f345dcab web: bump knip from 5.71.0 to 5.72.0 in /web (#18695)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.71.0 to 5.72.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/5.72.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 12:25:01 -05:00
Marc 'risson' Schmitt
a19a124352 core/sessions: remove django groups prefetch (#18704) 2025-12-09 17:16:08 +00:00
Jens L.
61be5d7c29 lib: add ak_create_jwt_raw (#18676)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-09 18:15:06 +01:00
dependabot[bot]
d728b74825 core: bump library/nginx from 553f64a to e21f8d0 in /website (#18698)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 16:16:43 +00:00
Simonyi Gergő
41050bb846 core: propagate ModuleNotFoundError in import_relative (#18683)
propagate `ModuleNotFoundError` in `import_relative`

Imagine adding a `signals.py` to some managed app and typing

```
import nonexistent_module
```

to it. Previously, this would silently fail and you'd try to debug your
signals instead of the import path. Now the error is noisy and explicit.
2025-12-09 16:58:03 +01:00
dependabot[bot]
01ed831663 core: bump goauthentik/fips-debian from 10c8086 to cb2d1f8 (#18696)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 16:55:27 +01:00
dependabot[bot]
a0bcb14a2f core: bump golang.org/x/sync from 0.18.0 to 0.19.0 (#18690)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 15:07:20 +00:00
dependabot[bot]
f8c3ccb32f core: bump golang.org/x/oauth2 from 0.33.0 to 0.34.0 (#18691)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 14:42:45 +00:00
Alexander Tereshkin
7e9e0a87f7 enterprise/reports: add users and events export (#18088)
* enterprise: add users and events export (reports app)

* enterprise/reports: replace assert with AsertionError so that the assumption check is not lost when compiling to optimised byte code

* enterprise/reports: use ConditionalInheritance with ExportMixin to make reduce coupling of enterprise with the rest of authentik

* enterprise/reports: use custom iterative File to save data export instead of accessing default_storage directly, so all the FileField.save logic can run correctly (e.g. creating directories)

* enterprise/reports: change app label to simply "authentik_reports"

* wip

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* update for new file api

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Apply suggestions from code review

Signed-off-by: Dominic R <dominic@sdko.org>

* wip

* sources/oauth: save returned oauth refresh tokens and add slack provider (#18501)

* sources/oauth: save returned oauth refresh tokens

* Update authentik/sources/oauth/models.py

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Connor Peshek <connor@connorpeshek.me>

* lint

* add tests

* fix proper id setting

* update id test

---------

Signed-off-by: Connor Peshek <connor@connorpeshek.me>
Co-authored-by: connor peshek <connorpeshek@unknown1641287c8f5d.attlocal.net>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: connor peshek <connorpeshek@connors-MacBook-Pro.local>

* core: custom avatar url improvements (#10525)

Co-authored-by: Dominic R <dominic@sdko.org>

* website/integrations: add salesforce (#18516)

Co-authored-by: connor peshek <connorpeshek@connors-MacBook-Pro.local>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>

* endpoints: implement endpoint stage (#18468)

* endpoints: implement endpoint stage

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix mismatched label

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix url in mdm config

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rephrase

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* and API & UI

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add deprecated support and deprecate gdtc

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add stage mode

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fixup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework stage slightly, add frontend

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* include jwks, add iat and exp

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* set kid

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* include device details in event list

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* implement device summary

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add remaining tables

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* revert sanitize

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix uuid format issues

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web/flows: update default background image (#18540)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* website/integrations: add hoop.dev (#17868)

Co-authored-by: iops <iops@syneforge.com>
Co-authored-by: Dominic R <dominic@sdko.org>

* website: Docusaurus 3.9.2 (#18506)

* endpoints/stage: v2, better error handling, more settings (#18545)

* add options, idle fallback

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* delete other device tokens during enroll

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better error handling

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* website: Glossary (#16007)

* website: Glossary

fix minor issues

wip

Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

anchor to param

wip

wip

at least the lockfile changes now

sure

a-z first as tana asked

idk why i switched in the first place

wip

wip

lock

lockfiles are hard

wip

please work

no have?

Revert "no have?"

This reverts commit 743dbc1bc2900eedcc2c93af248e6afdec3688a3.

* changed to sentence-case capitalization

---------

Co-authored-by: Tana M Berry <tana@goauthentik.io>

* web/i18n: Locale Context Merge Branch (#18426)

* web: Update fonts to Patternfly 5 variants.

* Fix order of heading override.

* web: Flesh out locale context.

* Fix Han pattern.

* Remove comment.

* Add additional regional codes.

* Clarify comment.

* Fix typos.

* web/i18n: Add locale-specific font overrides.

* Fix stale session in locale lifecycle.

* core, web: Fix Han language codes.

* Fix warnings about invalid BCP language code.

* Build translations.

* Add locale relative labels.

* Add locale translations for Finnish and Portuguese.

* Fix XLIFF errors.

* Clean up labels.

* Tidy regions.

* Match region comment.

* Update extracted values.

* Fix locale switch not triggering on source language.

* Split labels.

* Clean up labels.

* providers/scim: cache ServiceProviderConfig (#18047)

* Update authentik/enterprise/reports/api/reports.py

Co-authored-by: Jens L. <jens@beryju.org>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* enterprise/reports: got rid of unnecessary method-level import

* enterprise/reports: celan up code duplication in data export generation (invoke viewset.filter_queryset directly instead of replicating it)

* enterprise/reports: add check for app label when switching on content types

* enterprise/reports: make hyperlink field on Notification larger so it can fit the security token in the export file URL

* enterprise/reports: add is_superuser back in users export

* enterprise/reports: split tests into multiple files

* Apply suggestions from code review

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Fixed prettier issue

* Update web/src/admin/events/DataExportListPage.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* Update web/src/admin/events/DataExportListPage.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* Update web/src/admin/events/EventListPage.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* Update web/src/admin/reports/ExportButton.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* Update web/src/admin/reports/ExportButton.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* Update web/src/admin/users/UserListPage.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* Update web/src/elements/notifications/NotificationDrawer.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* Update web/src/elements/sidebar/SidebarItem.css

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>

* enterprise/reports: resolve code review merge errors

* enterprise/reports: remove the export button from the dom flow (by settings display:none) when there's no license

* enterprise/reports: improve docs

* include notification link in email

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* enterprise/reports: remove assignment assertion in ExportButton.ts

* cleanup tests after perm update

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Dominic R <dominic@sdko.org>
Signed-off-by: Connor Peshek <connor@connorpeshek.me>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Connor Peshek <connor@connorpeshek.me>
Co-authored-by: connor peshek <connorpeshek@unknown1641287c8f5d.attlocal.net>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: connor peshek <connorpeshek@connors-MacBook-Pro.local>
Co-authored-by: Konrad Mösch <konrad@moesch.org>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
Co-authored-by: shcherbak <ju.shcherbak@gmail.com>
Co-authored-by: iops <iops@syneforge.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
Co-authored-by: Jens L. <jens@beryju.org>
2025-12-09 09:35:41 -05:00
dependabot[bot]
ea513f2ec0 core: bump library/golang from 4f9d98e to b669435 (#18697)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 15:25:39 +01:00
authentik-automation[bot]
9093f5939b stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#18458)
* stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

# Conflicts:
#	web/package-lock.json

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-09 13:24:32 +01:00
dependabot[bot]
7b691d56a8 core: bump goauthentik.io/api/v3 from 3.2025120.18 to 3.2025120.19 (#18689)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025120.18 to 3.2025120.19.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2025120.18...v3.2025120.19)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025120.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 07:04:04 +01:00
authentik-automation[bot]
7bfe14c975 core, web: update translations (#18640)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-09 03:00:36 +01:00
Teffen Ellis
27f89ffad6 web: Improved table selection behavior (#18622)
* Fix caching issues when selecting a row.

* Adjust scroll alignment.

* Fix typo.
2025-12-08 17:20:48 -05:00
Dominic R
d5c743b4ee website/integrations: homarr: fix capitalization of redirect uri (#18679)
Signed-off-by: Dominic R <dominic@sdko.org>
2025-12-08 21:46:33 +00:00
Teffen Ellis
9b1f53766b web: Improved Timestamps (#18300)
* web: Fix issues which prevent timestamps from refreshing.

Clean up constants.

* web: Tidy types. Add timestamps.

* Fix `useDefault` with truthy value.
2025-12-08 16:42:36 -05:00
Teffen Ellis
4df1345c01 web: Hide device picker when challenges are not present. (#18611) 2025-12-08 19:18:47 +00:00
authentik-automation[bot]
08551f1b46 *: Auto compress images (#18673)
* *: compress images

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* ci trigger

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: gergosimonyi <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-08 14:18:31 +00:00
Dewi Roberts
6663cacfb4 website/integrations: update kimai doc (#18629)
* Update doc

* NameID
2025-12-08 14:06:02 +00:00
Jens L.
ff91edd70d root: skip current tab when refreshing others (#18674)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-08 14:57:36 +01:00
Simonyi Gergő
f7e23295ed core: add digraph group hierarchy (#17050)
* move imports

* core: add digraph group hierarchy

* move to permissions from Group or User to Role

* set group parents on frontend

* do not serialize `GroupParentageNode` directly

* core: enforce unique group name on database level

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use group parents in LDAP provider

* add user-role relationship control to frontend

* move materialized view to be more discoverable

* add guardian to mypy exceptions

* make `Role` a `ManagedModel`

* fixup! make `Role` a `ManagedModel`

* simplify `get_objects_for_user`

* fix flaky unit test

* rename `django-guardian` fork to `ak-guardian`

* add tests around users/groups/roles

* remove unused guardian config variable

* simplify guardian file structure

* clean up frontend

* initial docs

* remove `mode` from `InitialPermissions`

This is no longer needed, since users no longer directly have permissions.

* fixup! Merge branch 'main' into core/add-digraph-group-hierarchy

* clean up docs for managing permissions

* addendums from docs review

* fixup! Merge branch 'main' into core/add-digraph-group-hierarchy

* tweaks

* dewi and tana edits to docs

* tweak

* truly final tweaks, for now

* relabel Role Permissions table

* clarify button label

* fixup! Merge branch 'main' into core/add-digraph-group-hierarchy

* fixup! Merge branch 'main' into core/add-digraph-group-hierarchy

* merge migrations

* fixup! Merge branch 'main' into core/add-digraph-group-hierarchy

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-12-08 12:04:04 +01:00
dependabot[bot]
d54409c5dd core: bump astral-sh/uv from 0.9.15 to 0.9.16 (#18668)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.9.15 to 0.9.16.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.9.15...0.9.16)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.9.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:13:40 +01:00
dependabot[bot]
bebd725d25 core: bump goauthentik.io/api/v3 from 3.2025120.16 to 3.2025120.18 (#18661)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025120.16 to 3.2025120.18.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2025120.16...v3.2025120.18)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025120.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:13:16 +01:00
dependabot[bot]
a1ded8a837 web: bump type-fest from 5.3.0 to 5.3.1 in /web (#18663)
Bumps [type-fest](https://github.com/sindresorhus/type-fest) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/sindresorhus/type-fest/releases)
- [Commits](https://github.com/sindresorhus/type-fest/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: type-fest
  dependency-version: 5.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:11:39 +01:00
dependabot[bot]
7ea083f16c ci: bump peter-evans/create-pull-request from 7.0.9 to 7.0.11 (#18666)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.9 to 7.0.11.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](84ae59a2cd...22a9089034)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: 7.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:11:24 +01:00
dependabot[bot]
306921ac8a web: bump vite from 7.2.6 to 7.2.7 in /web (#18662)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.2.6 to 7.2.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.2.7/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:11:09 +01:00
dependabot[bot]
c255b086da core: bump goauthentik/fips-debian from a80dbbd to 10c8086 (#18665)
Bumps goauthentik/fips-debian from `a80dbbd` to `10c8086`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-debian
  dependency-version: trixie-slim-fips
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:10:48 +01:00
dependabot[bot]
35f6c9204c ci: bump actions/create-github-app-token from 2.2.0 to 2.2.1 (#18664)
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](7e473efe3c...29824e69f5)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: 2.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:10:32 +01:00
dependabot[bot]
a627396dcb ci: bump astral-sh/setup-uv from 7.1.4 to 7.1.5 in /.github/actions/setup (#18667)
ci: bump astral-sh/setup-uv in /.github/actions/setup

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.4 to 7.1.5.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](1e862dfacb...ed21f2f24f)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 7.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 11:09:40 +01:00
Dominic R
888733a32c website/docs: background tasks: add more detail about "next run" (#18660) 2025-12-08 09:07:32 +00:00
Dominic R
fa579c2ba5 website/docs: install-config: fix dump_config command (#18659) 2025-12-08 09:06:28 +00:00
Dominic R
8a200fd715 website/integrations: wordpress: fix redirect uri (#18658) 2025-12-08 09:06:10 +00:00
Jens L.
37ca47312d stages/mtls: always include cert in flow plan (#18657)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-08 01:58:21 +01:00
Jens L.
475ab76a5e endpoints: fix UI bugs, add user binding, etc (#18609)
* fix serializer for device user binding

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing import

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't expire enrollment tokens by default

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* slightly better config modal error handling

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add ability to bind to device

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add text when authenticating to device

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* prevent error when no authz flow is set

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add device to token log

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* address comments

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework config

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix expiring default

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't require page refresh for enrollment token to show up

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make config

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-08 01:13:29 +01:00
Jens L.
a0fe677efd sources/ldap: make server info optional (#18648)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-07 16:57:49 +01:00
Jens L.
3548d5e30d web/admin: fix event volume chart not updating with query (#18649)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-07 14:34:40 +01:00
dependabot[bot]
8e87585fce web: Bump types, fix ESLint errors (#17546)
* Fix config.

* Fix linter.

* Fix ts ignore comments.

* Fix empty functions

* Fix unnamed functions.

* Fix unused parameters.

* Fix define before use.

* Remove unused.

* Replace esbuild-copy-plugin with `fs` module.

---------

Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-12-06 20:21:29 +00:00
Teffen Ellis
31b0e73329 web: Fix row expansion on modal trigger buttons. (#18412)
web: Fix row expansion on modal triggers.
2025-12-06 12:10:17 -05:00
Connor Peshek
859a753e24 docs/integrations: add salesforce oauth source and SCIM steps (#18627) 2025-12-06 04:11:52 -06:00
829 changed files with 159516 additions and 16092 deletions

View File

@@ -12,16 +12,17 @@ inputs:
runs:
using: "composite"
steps:
- name: Install apt deps
- name: Install apt deps & cleanup
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
shell: bash
run: |
sudo apt-get remove --purge man-db
sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
sudo rm -rf /usr/local/lib/android
- name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v5
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v5
with:
enable-cache: true
- name: Setup python
@@ -50,13 +51,13 @@ runs:
if: ${{ contains(inputs.dependencies, 'runtime') }}
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
with:
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
- name: Setup dependencies
if: ${{ contains(inputs.dependencies, 'runtime') }}
shell: bash
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/docker-compose.yml up -d
docker compose -f .github/actions/setup/compose.yml up -d
cd web && npm i
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}

View File

@@ -11,11 +11,6 @@ services:
ports:
- 5432:5432
restart: always
redis:
image: docker.io/library/redis:7
ports:
- 6379:6379
restart: always
s3:
container_name: s3
image: docker.io/zenko/cloudserver

View File

@@ -8,19 +8,19 @@ inputs:
runs:
using: "composite"
steps:
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
with:
flags: ${{ inputs.flags }}
use_oidc: true
- uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
with:
flags: ${{ inputs.flags }}
file: unittest.xml
use_oidc: true
report_type: test_results
- name: PostgreSQL Logs
shell: bash
run: |
if [[ $ACTIONS_RUNNER_DEBUG == 'true' || $ACTIONS_STEP_DEBUG == 'true' ]]; then
if [[ $RUNNER_DEBUG == '1' ]]; then
docker stop setup-postgresql-1
echo "::group::PostgreSQL Logs"
docker logs setup-postgresql-1

View File

@@ -2,6 +2,10 @@
👋 Hi there! Welcome.
Please check the Contributing guidelines: https://docs.goauthentik.io/docs/developer-docs/#how-can-i-contribute
⚠️ IMPORTANT: Make sure you are opening this PR from a FEATURE BRANCH, not from your main branch!
If you opened this PR from your main branch, please close it and create a new feature branch instead.
For more information, see: https://docs.goauthentik.io/developer-docs/contributing/#always-use-feature-branches
-->
## Details

View File

@@ -44,7 +44,7 @@ jobs:
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -73,20 +73,19 @@ jobs:
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: Setup node
if: ${{ !inputs.release }}
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: generate ts client
if: ${{ !inputs.release }}
run: make gen-client-ts
- name: Build Docker Image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
id: push
with:
context: .
file: lifecycle/container/Dockerfile
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
@@ -97,7 +96,7 @@ jobs:
platforms: linux/${{ inputs.image_arch }}
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames }}:buildcache-${{ inputs.image_arch }}
cache-to: ${{ steps.ev.outputs.cacheTo }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
- uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:

View File

@@ -90,14 +90,14 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: int128/docker-manifest-create-action@b60433fd4312d7a64a56d769b76ebe3f45cf36b4 # v2
- uses: int128/docker-manifest-create-action@6cdd53a8337cd50bc3ef8c7016579d8d460edd94 # v2
id: build
with:
tags: ${{ matrix.tag }}
sources: |
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-amd64.outputs.image-digest }}
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-arm64.outputs.image-digest }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
- uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -46,7 +46,7 @@ jobs:
run: |
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
npm i @goauthentik/api@$VERSION
- uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}

View File

@@ -41,7 +41,7 @@ jobs:
- working-directory: website/
name: Install Dependencies
run: npm ci
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
- uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v4
with:
path: |
${{ github.workspace }}/website/api/.docusaurus
@@ -55,7 +55,7 @@ jobs:
env:
NODE_ENV: production
run: npm run build -w api
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: api-docs
path: website/api/build
@@ -67,7 +67,7 @@ jobs:
- build
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v5
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v5
with:
name: api-docs
path: website/api/build

View File

@@ -75,7 +75,7 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -101,7 +101,7 @@ jobs:
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max' || '' }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
- uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:

View File

@@ -24,5 +24,5 @@ jobs:
dir="/tmp/authentik/${{ matrix.version }}"
mkdir -p $dir
cd $dir
wget https://${{ matrix.version }}.goauthentik.io/docker-compose.yml
wget https://${{ matrix.version }}.goauthentik.io/compose.yml
${current}/scripts/test_docker.sh

View File

@@ -193,15 +193,17 @@ jobs:
glob: tests/e2e/test_source_scim*
- name: flows
glob: tests/e2e/test_flows*
- name: endpoints
glob: tests/e2e/test_endpoints_*
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Setup e2e env (chrome, etc)
run: |
docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull
docker compose -f tests/e2e/compose.yml up -d --quiet-pull
- id: cache-web
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
@@ -221,6 +223,54 @@ jobs:
if: ${{ always() }}
with:
flags: e2e
test-openid-conformance:
name: test-openid-conformance (${{ matrix.job.name }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
job:
- name: basic
glob: tests/openid_conformance/test_basic.py
- name: implicit
glob: tests/openid_conformance/test_implicit.py
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Setup e2e env (chrome, etc)
run: |
docker compose -f tests/e2e/compose.yml up -d --quiet-pull
- name: Setup conformance suite
run: |
docker compose -f tests/openid_conformance/compose.yml up -d --quiet-pull
- id: cache-web
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
run: |
npm ci
make -C .. gen-client-ts
npm run build
npm run build:sfe
- name: run conformance
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
uv run coverage xml
- uses: ./.github/actions/test-results
if: ${{ always() }}
with:
flags: conformance
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: conformance-certification-${{ matrix.job.name }}
path: tests/openid_conformance/exports/
ci-core-mark:
if: always()
needs:

View File

@@ -92,7 +92,7 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -114,7 +114,7 @@ jobs:
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: ${{ matrix.type }}.Dockerfile
file: lifecycle/container/${{ matrix.type }}.Dockerfile
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
@@ -122,7 +122,7 @@ jobs:
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
- uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:

View File

@@ -29,7 +29,7 @@ jobs:
github.event.pull_request.head.repo.full_name == github.repository)
steps:
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -42,7 +42,7 @@ jobs:
with:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
compressOnly: ${{ github.event_name != 'pull_request' }}
- uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}"
id: cpr
with:

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -26,7 +26,7 @@ jobs:
- name: Setup authentik env
uses: ./.github/actions/setup
- run: uv run ak update_webauthn_mds
- uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
if: ${{ env.GH_APP_ID != '' }}
with:
app-id: ${{ secrets.GH_APP_ID }}

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

View File

@@ -40,7 +40,7 @@ jobs:
registry-url: "https://registry.npmjs.org"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -57,7 +57,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -73,7 +73,7 @@ jobs:
- name: Bump version
run: "make bump version=${{ inputs.next_version }}.0-rc1"
- name: Create pull request
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
with:
token: ${{ steps.generate_token.outputs.token }}
branch: release-bump-${{ inputs.next_version }}

View File

@@ -35,7 +35,7 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -58,7 +58,7 @@ jobs:
push: true
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
- uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3
id: attest
if: true
with:
@@ -90,7 +90,7 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -121,10 +121,10 @@ jobs:
build-args: |
VERSION=${{ github.ref }}
tags: ${{ steps.ev.outputs.imageTags }}
file: ${{ matrix.type }}.Dockerfile
file: lifecycle/container/${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
- uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
@@ -232,7 +232,7 @@ jobs:
container=$(docker container create ${{ steps.ev.outputs.imageMainName }})
docker cp ${container}:web/ .
- name: Create a Sentry.io release
uses: getsentry/action-release@128c5058bbbe93c8e02147fe0a9c713f166259a6 # v3
uses: getsentry/action-release@dab6548b3c03c4717878099e43782cf5be654289 # v3
continue-on-error: true
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

View File

@@ -49,8 +49,12 @@ jobs:
test:
name: Pre-release test
runs-on: ubuntu-latest
needs:
- check-inputs
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
ref: "version-${{ needs.check-inputs.outputs.major_version }}"
- run: make test-docker
bump-authentik:
name: Bump authentik version
@@ -61,7 +65,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -108,7 +112,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -130,7 +134,7 @@ jobs:
sed -E -i 's/[0-9]{4}\.[0-9]{1,2}\.[0-9]+$/${{ inputs.version }}/' charts/authentik/Chart.yaml
./scripts/helm-docs.sh
- name: Create pull request
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
with:
token: "${{ steps.app-token.outputs.token }}"
branch: bump-${{ inputs.version }}
@@ -150,7 +154,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -185,7 +189,7 @@ jobs:
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url' version.json > version.new.json
mv version.new.json version.json
- name: Create pull request
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
with:
token: "${{ steps.app-token.outputs.token }}"
branch: bump-${{ inputs.version }}

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- id: generate_token
if: ${{ github.event_name != 'pull_request' }}
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -44,7 +44,7 @@ jobs:
make web-check-compile
- name: Create Pull Request
if: ${{ github.event_name != 'pull_request' }}
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
with:
token: ${{ steps.generate_token.outputs.token }}
branch: extract-compile-backend-translation

3
.gitignore vendored
View File

@@ -211,4 +211,5 @@ source_docs/
/vendor/
### Docker ###
docker-compose.override.yml
tests/openid_conformance/exports/*.zip
compose.override.yml

View File

@@ -16,10 +16,8 @@ go.sum @goauthentik/backend
# Infrastructure
.github/ @goauthentik/infrastructure
lifecycle/aws/ @goauthentik/infrastructure
Dockerfile @goauthentik/infrastructure
*Dockerfile @goauthentik/infrastructure
lifecycle/container/ @goauthentik/infrastructure
.dockerignore @goauthentik/infrastructure
docker-compose.yml @goauthentik/infrastructure
Makefile @goauthentik/infrastructure
.editorconfig @goauthentik/infrastructure
CODEOWNERS @goauthentik/infrastructure
@@ -28,8 +26,10 @@ packages/django-channels-postgres @goauthentik/backend
packages/django-postgres-cache @goauthentik/backend
packages/django-dramatiq-postgres @goauthentik/backend
# Web packages
packages/package.json @goauthentik/backend @goauthentik/frontend
packages/package-lock.json @goauthentik/backend @goauthentik/frontend
package.json @goauthentik/frontend
package-lock.json @goauthentik/frontend
packages/package.json @goauthentik/frontend
packages/package-lock.json @goauthentik/frontend
packages/docusaurus-config @goauthentik/frontend
packages/esbuild-plugin-live-reload @goauthentik/frontend
packages/eslint-config @goauthentik/frontend
@@ -38,7 +38,7 @@ packages/tsconfig @goauthentik/frontend
# Web
web/ @goauthentik/frontend
# Locale
locale/ @goauthentik/backend @goauthentik/frontend
/locale/ @goauthentik/backend @goauthentik/frontend
web/xliff/ @goauthentik/backend @goauthentik/frontend
# Docs
website/ @goauthentik/docs

View File

@@ -9,6 +9,13 @@ NPM_VERSION = $(shell python -m scripts.generate_semver)
PY_SOURCES = authentik packages tests scripts lifecycle .github
DOCKER_IMAGE ?= "authentik:test"
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SED_INPLACE = sed -i ''
else
SED_INPLACE = sed -i
endif
GEN_API_TS = gen-ts-api
GEN_API_PY = gen-py-api
GEN_API_GO = gen-go-api
@@ -119,8 +126,8 @@ bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
ifndef version
$(error Usage: make bump version=20xx.xx.xx )
endif
sed -i 's/^version = ".*"/version = "$(version)"/' pyproject.toml
sed -i 's/^VERSION = ".*"/VERSION = "$(version)"/' authentik/__init__.py
$(SED_INPLACE) 's/^version = ".*"/version = "$(version)"/' pyproject.toml
$(SED_INPLACE) 's/^VERSION = ".*"/VERSION = "$(version)"/' authentik/__init__.py
$(MAKE) gen-build gen-compose aws-cfn
npm version --no-git-tag-version --allow-same-version $(version)
cd ${PWD}/web && npm version --no-git-tag-version --allow-same-version $(version)
@@ -134,14 +141,10 @@ gen-build: ## Extract the schema from the database
AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak make_blueprint_schema --file blueprints/schema.json
AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak spectacular --file schema.yml
uv run ak build_schema
gen-compose:
uv run scripts/generate_docker_compose.py
uv run scripts/generate_compose.py
gen-changelog: ## (Release) generate the changelog based from the commits since the last tag
git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md
@@ -149,14 +152,14 @@ gen-changelog: ## (Release) generate the changelog based from the commits since
gen-diff: ## (Release) generate the changelog diff between the current schema and the last tag
git show $(shell git describe --tags $(shell git rev-list --tags --max-count=1)):schema.yml > schema-old.yml
docker compose -f scripts/api/docker-compose.yml run --rm --user "${UID}:${GID}" diff \
docker compose -f scripts/api/compose.yml run --rm --user "${UID}:${GID}" diff \
--markdown \
/local/diff.md \
/local/schema-old.yml \
/local/schema.yml
rm schema-old.yml
sed -i 's/{/&#123;/g' diff.md
sed -i 's/}/&#125;/g' diff.md
$(SED_INPLACE) 's/{/&#123;/g' diff.md
$(SED_INPLACE) 's/}/&#125;/g' diff.md
npx prettier --write diff.md
gen-clean-ts: ## Remove generated API client for TypeScript
@@ -172,7 +175,7 @@ gen-clean-go: ## Remove generated API client for Go
gen-clean: gen-clean-ts gen-clean-go gen-clean-py ## Remove generated API clients
gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescript into the authentik UI Application
docker compose -f scripts/api/docker-compose.yml run --rm --user "${UID}:${GID}" gen \
docker compose -f scripts/api/compose.yml run --rm --user "${UID}:${GID}" gen \
generate \
-i /local/schema.yml \
-g typescript-fetch \
@@ -293,7 +296,7 @@ docs-api-clean: ## Clean generated API documentation
docker: ## Build a docker image of the current source tree
mkdir -p ${GEN_API_TS}
DOCKER_BUILDKIT=1 docker build . --progress plain --tag ${DOCKER_IMAGE}
DOCKER_BUILDKIT=1 docker build . -f lifecycle/container/Dockerfile --progress plain --tag ${DOCKER_IMAGE}
test-docker:
BUILD=true ${PWD}/scripts/test_docker.sh
@@ -327,6 +330,6 @@ ci-pending-migrations: ci--meta-debug
uv run ak makemigrations --check
ci-test: ci--meta-debug
uv run coverage run manage.py test --keepdb --randomly-seed ${CI_TEST_SEED} authentik
uv run coverage run manage.py test --keepdb authentik
uv run coverage report
uv run coverage xml

View File

@@ -3,7 +3,7 @@
from functools import lru_cache
from os import environ
VERSION = "2025.12.0-rc1"
VERSION = "2026.2.0-rc1"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@@ -37,7 +37,7 @@ class VersionSerializer(PassiveSerializer):
def get_version_latest(self, _) -> str:
"""Get latest version from cache"""
if get_current_tenant().schema_name == get_public_schema_name():
if get_current_tenant().schema_name != get_public_schema_name():
return authentik_version()
version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache: # pragma: no cover

View File

@@ -240,7 +240,9 @@ class FileUsedByView(APIView):
for field in fields:
q |= Q(**{field: params.get("name")})
objs = get_objects_for_user(request.user, f"{app}.view_{model_name}", model)
objs = get_objects_for_user(
request.user, f"{app}.view_{model_name}", model.objects.all()
)
objs = objs.filter(q)
for obj in objs:
serializer = UsedBySerializer(

View File

@@ -1,10 +1,13 @@
from collections.abc import Generator, Iterator
from collections.abc import Callable, Generator, Iterator
from typing import cast
from django.core.cache import cache
from django.http.request import HttpRequest
from structlog.stdlib import get_logger
from authentik.admin.files.usage import FileUsage
CACHE_PREFIX = "goauthentik.io/admin/files"
LOGGER = get_logger()
@@ -53,13 +56,19 @@ class Backend:
"""
raise NotImplementedError
def file_url(self, name: str, request: HttpRequest | None = None) -> str:
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""
Get URL for accessing the file.
Args:
file_path: Relative file path
request: Optional Django HttpRequest for fully qualifed URL building
use_cache: whether to retrieve the URL from cache
Returns:
URL to access the file (may be relative or absolute depending on backend)
@@ -132,3 +141,22 @@ class ManageableBackend(Backend):
True if file exists, False otherwise
"""
raise NotImplementedError
def _cache_get_or_set(
self,
name: str,
request: HttpRequest | None,
default: Callable[[str, HttpRequest | None], str],
timeout: int,
) -> str:
timeout_ignore = 60
timeout = int(timeout * 0.67)
if timeout < timeout_ignore:
timeout = 0
request_key = "None"
if request is not None:
request_key = f"{request.build_absolute_uri('/')}"
cache_key = f"{CACHE_PREFIX}/{self.name}/{self.usage}/{request_key}/{name}"
return cast(str, cache.get_or_set(cache_key, lambda: default(name, request), timeout))

View File

@@ -63,7 +63,12 @@ class FileBackend(ManageableBackend):
rel_path = full_path.relative_to(self.base_path)
yield str(rel_path)
def file_url(self, name: str, request: HttpRequest | None = None) -> str:
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Get URL for accessing the file."""
expires_in = timedelta_from_string(
CONFIG.get(
@@ -72,21 +77,28 @@ class FileBackend(ManageableBackend):
)
)
prefix = CONFIG.get("web.path", "/")[:-1]
path = f"{self.usage.value}/{connection.schema_name}/{name}"
token = jwt.encode(
payload={
"path": path,
"exp": now() + expires_in,
"nbf": now() - timedelta(seconds=15),
},
key=sha256(f"{settings.SECRET_KEY}:{self.usage}".encode()).hexdigest(),
algorithm="HS256",
)
url = f"{prefix}/files/{path}?token={token}"
if request is None:
return url
return request.build_absolute_uri(url)
def _file_url(name: str, request: HttpRequest | None) -> str:
prefix = CONFIG.get("web.path", "/")[:-1]
path = f"{self.usage.value}/{connection.schema_name}/{name}"
token = jwt.encode(
payload={
"path": path,
"exp": now() + expires_in,
"nbf": now() - timedelta(seconds=15),
},
key=sha256(f"{settings.SECRET_KEY}:{self.usage}".encode()).hexdigest(),
algorithm="HS256",
)
url = f"{prefix}/files/{path}?token={token}"
if request is None:
return url
return request.build_absolute_uri(url)
if use_cache:
timeout = int(expires_in.total_seconds())
return self._cache_get_or_set(name, request, _file_url, timeout)
else:
return _file_url(name, request)
def save_file(self, name: str, content: bytes) -> None:
"""Save file to local filesystem."""

View File

@@ -38,6 +38,11 @@ class PassthroughBackend(Backend):
"""External files cannot be listed."""
yield from []
def file_url(self, name: str, request: HttpRequest | None = None) -> str:
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Return the URL as-is for passthrough files."""
return name

View File

@@ -130,44 +130,57 @@ class S3Backend(ManageableBackend):
if rel_path: # Skip if it's just the directory itself
yield rel_path
def file_url(self, name: str, request: HttpRequest | None = None) -> str:
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Generate presigned URL for file access."""
use_https = CONFIG.get_bool(
f"storage.{self.usage.value}.{self.name}.secure_urls",
CONFIG.get_bool(f"storage.{self.name}.secure_urls", True),
)
params = {
"Bucket": self.bucket_name,
"Key": f"{self.base_path}/{name}",
}
expires_in = int(
timedelta_from_string(
CONFIG.get(
f"storage.{self.usage.value}.{self.name}.url_expiry",
CONFIG.get(f"storage.{self.name}.url_expiry", "minutes=15"),
)
).total_seconds()
)
expires_in = timedelta_from_string(
CONFIG.get(
f"storage.{self.usage.value}.{self.name}.url_expiry",
CONFIG.get(f"storage.{self.name}.url_expiry", "minutes=15"),
def _file_url(name: str, request: HttpRequest | None) -> str:
params = {
"Bucket": self.bucket_name,
"Key": f"{self.base_path}/{name}",
}
url = self.client.generate_presigned_url(
"get_object",
Params=params,
ExpiresIn=expires_in,
HttpMethod="GET",
)
)
url = self.client.generate_presigned_url(
"get_object",
Params=params,
ExpiresIn=expires_in.total_seconds(),
HttpMethod="GET",
)
# Support custom domain for S3-compatible storage (so not AWS)
# Well, can't you do custom domains on AWS as well?
custom_domain = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.custom_domain",
CONFIG.get(f"storage.{self.name}.custom_domain", None),
)
if custom_domain:
parsed = urlsplit(url)
scheme = "https" if use_https else "http"
url = f"{scheme}://{custom_domain}{parsed.path}?{parsed.query}"
# Support custom domain for S3-compatible storage (so not AWS)
# Well, can't you do custom domains on AWS as well?
custom_domain = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.custom_domain",
CONFIG.get(f"storage.{self.name}.custom_domain", None),
)
if custom_domain:
parsed = urlsplit(url)
scheme = "https" if use_https else "http"
url = f"{scheme}://{custom_domain}{parsed.path}?{parsed.query}"
return url
return url
if use_cache:
return self._cache_get_or_set(name, request, _file_url, expires_in)
else:
return _file_url(name, request)
def save_file(self, name: str, content: bytes) -> None:
"""Save file to S3."""

View File

@@ -44,7 +44,12 @@ class StaticBackend(Backend):
if file_path.is_file() and (file_path.suffix in STATIC_FILE_EXTENSIONS):
yield f"{STATIC_PATH_PREFIX}/dist/{dir}/{file_path.name}"
def file_url(self, name: str, request: HttpRequest | None = None) -> str:
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Get URL for static file."""
prefix = CONFIG.get("web.path", "/")[:-1]
url = f"{prefix}{name}"

View File

@@ -1,10 +1,13 @@
from unittest import skipUnless
from django.test import TestCase
from authentik.admin.files.tests.utils import FileTestS3BackendMixin
from authentik.admin.files.tests.utils import FileTestS3BackendMixin, s3_test_server_available
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
@skipUnless(s3_test_server_available(), "S3 test server not available")
class TestS3Backend(FileTestS3BackendMixin, TestCase):
"""Test S3 backend functionality"""

View File

@@ -70,6 +70,7 @@ class FileManager:
self,
name: str | None,
request: HttpRequest | Request | None = None,
use_cache: bool = True,
) -> str:
"""
Get URL for accessing the file.

View File

@@ -1,10 +1,16 @@
"""Test file service layer"""
from unittest import skipUnless
from django.http import HttpRequest
from django.test import TestCase
from authentik.admin.files.manager import FileManager
from authentik.admin.files.tests.utils import FileTestFileBackendMixin, FileTestS3BackendMixin
from authentik.admin.files.tests.utils import (
FileTestFileBackendMixin,
FileTestS3BackendMixin,
s3_test_server_available,
)
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
@@ -81,6 +87,7 @@ class TestResolveFileUrlFileBackend(FileTestFileBackendMixin, TestCase):
self.assertEqual(result, "http://example.com/files/media/public/test.png")
@skipUnless(s3_test_server_available(), "S3 test server not available")
class TestResolveFileUrlS3Backend(FileTestS3BackendMixin, TestCase):
@CONFIG.patch("storage.media.s3.custom_domain", "s3.test:8080/test")
@CONFIG.patch("storage.media.s3.secure_urls", False)

View File

@@ -1,11 +1,26 @@
import shutil
import socket
from tempfile import mkdtemp
from urllib.parse import urlparse
from authentik.admin.files.backends.s3 import S3Backend
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG, UNSET
from authentik.lib.generators import generate_id
S3_TEST_ENDPOINT = "http://localhost:8020"
def s3_test_server_available() -> bool:
"""Check if the S3 test server is reachable."""
parsed = urlparse(S3_TEST_ENDPOINT)
try:
with socket.create_connection((parsed.hostname, parsed.port), timeout=2):
return True
except OSError:
return False
class FileTestFileBackendMixin:
def setUp(self):
@@ -57,7 +72,7 @@ class FileTestS3BackendMixin:
for key in s3_config_keys:
self.original_media_s3_settings[key] = CONFIG.get(f"storage.media.s3.{key}", UNSET)
self.media_s3_bucket_name = f"authentik-test-{generate_id(10)}".lower()
CONFIG.set("storage.media.s3.endpoint", "http://localhost:8020")
CONFIG.set("storage.media.s3.endpoint", S3_TEST_ENDPOINT)
CONFIG.set("storage.media.s3.access_key", "accessKey1")
CONFIG.set("storage.media.s3.secret_key", "secretKey1")
CONFIG.set("storage.media.s3.bucket_name", self.media_s3_bucket_name)
@@ -70,7 +85,7 @@ class FileTestS3BackendMixin:
for key in s3_config_keys:
self.original_reports_s3_settings[key] = CONFIG.get(f"storage.reports.s3.{key}", UNSET)
self.reports_s3_bucket_name = f"authentik-test-{generate_id(10)}".lower()
CONFIG.set("storage.reports.s3.endpoint", "http://localhost:8020")
CONFIG.set("storage.reports.s3.endpoint", S3_TEST_ENDPOINT)
CONFIG.set("storage.reports.s3.access_key", "accessKey1")
CONFIG.set("storage.reports.s3.secret_key", "secretKey1")
CONFIG.set("storage.reports.s3.bucket_name", self.reports_s3_bucket_name)

View File

@@ -70,6 +70,9 @@ class IPCUser(AnonymousUser):
def is_authenticated(self):
return True
def all_roles(self):
return []
class TokenAuthentication(BaseAuthentication):
"""Token-based authentication using HTTP Bearer authentication"""

View File

View File

@@ -0,0 +1,45 @@
from json import dumps
from django.core.management.base import BaseCommand, no_translations
from drf_spectacular.drainage import GENERATOR_STATS
from drf_spectacular.generators import SchemaGenerator
from drf_spectacular.renderers import OpenApiYamlRenderer
from drf_spectacular.validation import validate_schema
from structlog.stdlib import get_logger
from authentik.blueprints.v1.schema import SchemaBuilder
class Command(BaseCommand):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_logger()
def add_arguments(self, parser):
parser.add_argument("--blueprint-file", type=str, default="blueprints/schema.json")
parser.add_argument("--api-file", type=str, default="schema.yml")
@no_translations
def handle(self, *args, blueprint_file: str, api_file: str, **options):
self.build_blueprint(blueprint_file)
self.build_api(api_file)
def build_blueprint(self, file: str):
self.logger.debug("Building blueprint schema...", file=file)
blueprint_builder = SchemaBuilder()
blueprint_builder.build()
with open(file, "w") as _schema:
_schema.write(
dumps(blueprint_builder.schema, indent=4, default=SchemaBuilder.json_default)
)
def build_api(self, file: str):
self.logger.debug("Building API schema...", file=file)
generator = SchemaGenerator()
schema = generator.get_schema(request=None, public=True)
GENERATOR_STATS.emit_summary()
validate_schema(schema)
output = OpenApiYamlRenderer().render(schema, renderer_context={})
with open(file, "wb") as f:
f.write(output)

View File

@@ -13,6 +13,13 @@ class Pagination(pagination.PageNumberPagination):
page_query_param = "page"
page_size_query_param = "page_size"
def get_page_size(self, request):
if self.page_size_query_param in request.query_params:
page_size = super().get_page_size(request)
if page_size is not None:
return min(super().get_page_size(request), request.tenant.pagination_max_page_size)
return request.tenant.pagination_default_page_size
def get_paginated_response(self, data):
previous_page_number = 0
if self.page.has_previous():

View File

@@ -1,9 +1,14 @@
"""Schema generation tests"""
from pathlib import Path
from django.core.management import call_command
from django.urls import reverse
from rest_framework.test import APITestCase
from yaml import safe_load
from authentik.lib.config import CONFIG
class TestSchemaGeneration(APITestCase):
"""Generic admin tests"""
@@ -21,3 +26,18 @@ class TestSchemaGeneration(APITestCase):
reverse("authentik_api:schema-browser"),
)
self.assertEqual(response.status_code, 200)
def test_build_schema(self):
"""Test schema build command"""
blueprint_file = Path("blueprints/schema.json")
api_file = Path("schema.yml")
blueprint_file.unlink()
api_file.unlink()
with (
CONFIG.patch("debug", True),
CONFIG.patch("tenants.enabled", True),
CONFIG.patch("outposts.disable_embedded_outpost", True),
):
call_command("build_schema")
self.assertTrue(blueprint_file.exists())
self.assertTrue(api_file.exists())

View File

@@ -31,6 +31,7 @@ class Capabilities(models.TextChoices):
"""Define capabilities which influence which APIs can/should be used"""
CAN_SAVE_MEDIA = "can_save_media"
CAN_SAVE_REPORTS = "can_save_reports"
CAN_GEO_IP = "can_geo_ip"
CAN_ASN = "can_asn"
CAN_IMPERSONATE = "can_impersonate"
@@ -70,6 +71,8 @@ class ConfigView(APIView):
caps = []
if get_file_manager(FileUsage.MEDIA).manageable:
caps.append(Capabilities.CAN_SAVE_MEDIA)
if get_file_manager(FileUsage.REPORTS).manageable:
caps.append(Capabilities.CAN_SAVE_REPORTS)
for processor in get_context_processors():
if cap := processor.capability():
caps.append(cap)

View File

@@ -1,10 +1,12 @@
"""authentik Blueprints app"""
import traceback
from collections.abc import Callable
from importlib import import_module
from inspect import ismethod
from django.apps import AppConfig
from django.conf import settings
from django.db import DatabaseError, InternalError, ProgrammingError
from dramatiq.broker import get_broker
from structlog.stdlib import BoundLogger, get_logger
@@ -44,8 +46,21 @@ class ManagedAppConfig(AppConfig):
module_name = f"{self.name}.{rel_module}"
import_module(module_name)
self.logger.info("Imported related module", module=module_name)
except ModuleNotFoundError:
pass
except ModuleNotFoundError as exc:
if settings.DEBUG:
# This is a heuristic for determining whether the exception was caused
# "directly" by the `import_module` call or whether the initial import
# succeeded and a later import (within the existing module) failed.
# 1. <the calling function>
# 2. importlib.import_module
# 3. importlib._bootstrap._gcd_import
# 4. importlib._bootstrap._find_and_load
# 5. importlib._bootstrap._find_and_load_unlocked
STACK_LENGTH_HEURISTIC = 5
stack_length = len(traceback.extract_tb(exc.__traceback__))
if stack_length > STACK_LENGTH_HEURISTIC:
raise
import_relative("checks")
import_relative("tasks")

View File

@@ -8,45 +8,62 @@ metadata:
- Application (icon)
- Source (icon)
- Flow (background)
- Endpoint Enrollment token (key)
entries:
- model: authentik_core.token
identifiers:
identifier: "%(uid)s-token"
attrs:
key: "%(uid)s"
user: "%(user)s"
intent: api
- model: authentik_core.application
identifiers:
slug: "%(uid)s-app"
attrs:
name: "%(uid)s-app"
icon: https://goauthentik.io/img/icon.png
- model: authentik_sources_oauth.oauthsource
identifiers:
slug: "%(uid)s-source"
attrs:
name: "%(uid)s-source"
provider_type: azuread
consumer_key: "%(uid)s"
consumer_secret: "%(uid)s"
icon: https://goauthentik.io/img/icon.png
- model: authentik_flows.flow
identifiers:
slug: "%(uid)s-flow"
attrs:
name: "%(uid)s-flow"
title: "%(uid)s-flow"
designation: authentication
background: https://goauthentik.io/img/icon.png
- model: authentik_core.user
identifiers:
username: "%(uid)s"
attrs:
name: "%(uid)s"
password: "%(uid)s"
- model: authentik_core.user
identifiers:
username: "%(uid)s-no-password"
attrs:
name: "%(uid)s"
token:
- model: authentik_core.token
identifiers:
identifier: "%(uid)s-token"
attrs:
key: "%(uid)s"
user: "%(user)s"
intent: api
app:
- model: authentik_core.application
identifiers:
slug: "%(uid)s-app"
attrs:
name: "%(uid)s-app"
icon: https://goauthentik.io/img/icon.png
source:
- model: authentik_sources_oauth.oauthsource
identifiers:
slug: "%(uid)s-source"
attrs:
name: "%(uid)s-source"
provider_type: azuread
consumer_key: "%(uid)s"
consumer_secret: "%(uid)s"
icon: https://goauthentik.io/img/icon.png
flow:
- model: authentik_flows.flow
identifiers:
slug: "%(uid)s-flow"
attrs:
name: "%(uid)s-flow"
title: "%(uid)s-flow"
designation: authentication
background: https://goauthentik.io/img/icon.png
user:
- model: authentik_core.user
identifiers:
username: "%(uid)s"
attrs:
name: "%(uid)s"
password: "%(uid)s"
- model: authentik_core.user
identifiers:
username: "%(uid)s-no-password"
attrs:
name: "%(uid)s"
endpoint:
- model: authentik_endpoints_connectors_agent.agentconnector
id: connector
identifiers:
name: "%(uid)s"
- model: authentik_endpoints_connectors_agent.enrollmenttoken
identifiers:
name: "%(uid)s"
attrs:
key: "%(uid)s"
connector: !KeyOf connector

View File

@@ -5,6 +5,7 @@ from django.test import TransactionTestCase
from authentik.blueprints.v1.importer import Importer
from authentik.core.models import Token, User
from authentik.core.tests.utils import create_test_admin_user
from authentik.endpoints.connectors.agent.models import EnrollmentToken
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
@@ -29,12 +30,18 @@ class TestBlueprintsV1ConditionalFields(TransactionTestCase):
def test_user(self):
"""Test user"""
user: User = User.objects.filter(username=self.uid).first()
user = User.objects.filter(username=self.uid).first()
self.assertIsNotNone(user)
self.assertTrue(user.check_password(self.uid))
def test_user_null(self):
"""Test user"""
user: User = User.objects.filter(username=f"{self.uid}-no-password").first()
user = User.objects.filter(username=f"{self.uid}-no-password").first()
self.assertIsNotNone(user)
self.assertFalse(user.has_usable_password())
def test_enrollment_token(self):
"""Test endpoint enrollment token"""
token = EnrollmentToken.objects.filter(name=self.uid).first()
self.assertIsNotNone(token)
self.assertEqual(token.key, self.uid)

View File

@@ -36,10 +36,7 @@ class TestBlueprintsV1RBAC(TransactionTestCase):
self.assertTrue(importer.apply())
role = Role.objects.filter(name=uid).first()
self.assertIsNotNone(role)
self.assertEqual(
list(role.group.permissions.all().values_list("codename", flat=True)),
["view_blueprintinstance"],
)
self.assertEqual(get_perms(role), {"authentik_blueprints.view_blueprintinstance"})
def test_object_permission(self):
"""Test permissions"""
@@ -53,5 +50,5 @@ class TestBlueprintsV1RBAC(TransactionTestCase):
user = User.objects.filter(username=uid).first()
role = Role.objects.filter(name=uid).first()
self.assertIsNotNone(flow)
self.assertEqual(get_perms(user, flow), ["view_flow"])
self.assertEqual(get_perms(role.group, flow), ["view_flow"])
self.assertEqual(get_perms(user, flow), {"authentik_flows.view_flow"})
self.assertEqual(get_perms(role, flow), {"authentik_flows.view_flow"})

View File

@@ -149,7 +149,7 @@ class TestBlueprintsV1Tasks(TransactionTestCase):
instance.status,
BlueprintInstanceStatus.UNKNOWN,
)
apply_blueprint(instance.pk)
apply_blueprint.send(instance.pk).get_result(block=True)
instance.refresh_from_db()
self.assertEqual(instance.last_applied_hash, "")
self.assertEqual(

View File

@@ -15,9 +15,7 @@ from django.db.models import Model
from django.db.models.query_utils import Q
from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django_channels_postgres.models import GroupChannel, Message
from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm
from guardian.models import RoleObjectPermission, UserObjectPermission
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import BaseSerializer, Serializer
from structlog.stdlib import BoundLogger, get_logger
@@ -42,55 +40,17 @@ from authentik.core.models import (
User,
UserSourceConnection,
)
from authentik.endpoints.connectors.agent.models import (
AgentDeviceConnection,
AppleNonce,
DeviceAuthenticationToken,
)
from authentik.endpoints.connectors.agent.models import (
DeviceToken as EndpointDeviceToken,
)
from authentik.endpoints.models import Connector, Device, DeviceConnection, DeviceFactSnapshot
from authentik.endpoints.models import Connector
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import LicenseUsage
from authentik.enterprise.providers.google_workspace.models import (
GoogleWorkspaceProviderGroup,
GoogleWorkspaceProviderUser,
)
from authentik.enterprise.providers.microsoft_entra.models import (
MicrosoftEntraProviderGroup,
MicrosoftEntraProviderUser,
)
from authentik.enterprise.providers.ssf.models import StreamEvent
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
EndpointDevice,
EndpointDeviceConnection,
)
from authentik.events.logs import LogEvent, capture_logs
from authentik.events.utils import cleanse_dict
from authentik.flows.models import FlowToken, Stage
from authentik.lib.models import SerializerModel
from authentik.flows.models import Stage
from authentik.lib.models import InternallyManagedMixin, SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.reflection import get_apps
from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation
from authentik.providers.oauth2.models import (
AccessToken,
AuthorizationCode,
DeviceToken,
RefreshToken,
)
from authentik.providers.proxy.models import ProxySession
from authentik.providers.rac.models import ConnectionToken
from authentik.providers.saml.models import SAMLSession
from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
from authentik.rbac.models import Role
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType
from authentik.stages.consent.models import UserConsent
from authentik.tasks.models import Task, TaskLog
from authentik.tenants.models import Tenant
# Context set when the serializer is created in a blueprint context
# Update website/docs/customize/blueprints/v1/models.md when used
@@ -110,6 +70,7 @@ def excluded_models() -> list[type[Model]]:
DjangoGroup,
ContentType,
Permission,
RoleObjectPermission,
UserObjectPermission,
# Base classes
Provider,
@@ -125,49 +86,16 @@ def excluded_models() -> list[type[Model]]:
# Classes that have other dependencies
Session,
AuthenticatedSession,
# Classes which are only internally managed
# FIXME: these shouldn't need to be explicitly listed, but rather based off of a mixin
FlowToken,
LicenseUsage,
SCIMProviderGroup,
SCIMProviderUser,
Tenant,
Task,
TaskLog,
ConnectionToken,
AuthorizationCode,
AccessToken,
RefreshToken,
ProxySession,
Reputation,
WebAuthnDeviceType,
SCIMSourceUser,
SCIMSourceGroup,
GoogleWorkspaceProviderUser,
GoogleWorkspaceProviderGroup,
MicrosoftEntraProviderUser,
MicrosoftEntraProviderGroup,
EndpointDevice,
EndpointDeviceConnection,
EndpointDeviceToken,
Device,
DeviceConnection,
DeviceAuthenticationToken,
AppleNonce,
AgentDeviceConnection,
DeviceFactSnapshot,
DeviceToken,
StreamEvent,
UserConsent,
SAMLSession,
Message,
GroupChannel,
)
def is_model_allowed(model: type[Model]) -> bool:
"""Check if model is allowed"""
return model not in excluded_models() and issubclass(model, SerializerModel | BaseMetaModel)
return (
model not in excluded_models()
and issubclass(model, SerializerModel | BaseMetaModel)
and not issubclass(model, InternallyManagedMixin)
)
class DoRollback(SentryIgnoredException):
@@ -394,10 +322,12 @@ class Importer:
"""Apply object-level permissions for an entry"""
for perm in entry.get_permissions(self._import):
if perm.user is not None:
assign_perm(perm.permission, User.objects.get(pk=perm.user), instance)
User.objects.get(pk=perm.user).assign_perms_to_managed_role(
perm.permission, instance
)
if perm.role is not None:
role = Role.objects.get(pk=perm.role)
role.assign_permission(perm.permission, obj=instance)
role.assign_perms(perm.permission, obj=instance)
def apply(self) -> bool:
"""Apply (create/update) models yaml, in database transaction"""

View File

@@ -37,14 +37,21 @@ class ApplyBlueprintMetaSerializer(PassiveSerializer):
return super().validate(attrs)
def create(self, validated_data: dict) -> MetaResult:
from authentik.blueprints.v1.tasks import apply_blueprint
from authentik.blueprints.v1.importer import Importer
if not self.blueprint_instance:
LOGGER.info("Blueprint does not exist, but not required")
return MetaResult()
LOGGER.debug("Applying blueprint from meta model", blueprint=self.blueprint_instance)
apply_blueprint(self.blueprint_instance.pk)
# Apply blueprint directly using Importer to avoid task context requirements
# and prevent deadlocks when called from within another blueprint task
blueprint_content = self.blueprint_instance.retrieve()
importer = Importer.from_string(blueprint_content, self.blueprint_instance.context)
valid, logs = importer.validate()
[log.log() for log in logs]
if valid:
importer.apply()
return MetaResult()

View File

@@ -1,10 +1,9 @@
"""Generate JSON Schema for blueprints"""
from json import dumps
from typing import Any
from django.core.management.base import BaseCommand, no_translations
from django.db.models import Model, fields
from django.db.models.fields.related import OneToOneField
from drf_jsonschema_serializer.convert import converter, field_to_converter
from rest_framework.fields import Field, JSONField, UUIDField
from rest_framework.relations import PrimaryKeyRelatedField
@@ -32,18 +31,19 @@ class PrimaryKeyRelatedFieldConverter:
def convert(self, field: PrimaryKeyRelatedField):
model: Model = field.queryset.model
pk_field = model._meta.pk
if isinstance(pk_field, OneToOneField):
pk_field = pk_field.related_fields[0][1]
if isinstance(pk_field, fields.UUIDField):
return {"type": "string", "format": "uuid"}
return {"type": "integer"}
class Command(BaseCommand):
class SchemaBuilder:
"""Generate JSON Schema for blueprints"""
schema: dict
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self):
self.schema = {
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
@@ -90,16 +90,6 @@ class Command(BaseCommand):
"$defs": {"blueprint_entry": {"oneOf": []}},
}
def add_arguments(self, parser):
parser.add_argument("--file", type=str)
@no_translations
def handle(self, *args, file: str, **options):
"""Generate JSON Schema for blueprints"""
self.build()
with open(file, "w") as _schema:
_schema.write(dumps(self.schema, indent=4, default=Command.json_default))
@staticmethod
def json_default(value: Any) -> Any:
"""Helper that handles gettext_lazy strings that JSON doesn't handle"""
@@ -121,7 +111,7 @@ class Command(BaseCommand):
try:
serializer_class = model_instance.serializer
except NotImplementedError as exc:
raise NotImplementedError(model_instance) from exc
raise ValueError(f"SerializerModel not implemented by {model}") from exc
serializer = serializer_class(
context={
SERIALIZER_CONTEXT_BLUEPRINT: False,

View File

@@ -12,7 +12,6 @@ from django.db import DatabaseError, InternalError, ProgrammingError
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_dramatiq_postgres.middleware import CurrentTaskNotFound
from dramatiq.actor import actor
from dramatiq.middleware import Middleware
from structlog.stdlib import get_logger
@@ -40,7 +39,6 @@ from authentik.events.utils import sanitize_dict
from authentik.lib.config import CONFIG
from authentik.tasks.apps import PRIORITY_HIGH
from authentik.tasks.middleware import CurrentTask
from authentik.tasks.models import Task
from authentik.tasks.schedules.models import Schedule
from authentik.tenants.models import Tenant
@@ -191,10 +189,7 @@ def check_blueprint_v1_file(blueprint: BlueprintFile):
@actor(description=_("Apply single blueprint."))
def apply_blueprint(instance_pk: UUID):
try:
self = CurrentTask.get_task()
except CurrentTaskNotFound:
self = Task()
self = CurrentTask.get_task()
self.set_uid(str(instance_pk))
instance: BlueprintInstance | None = None
try:

View File

@@ -4,7 +4,8 @@ from collections.abc import Iterator
from copy import copy
from django.core.cache import cache
from django.db.models import QuerySet
from django.db.models import Case, QuerySet
from django.db.models.expressions import When
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
@@ -22,6 +23,7 @@ from authentik.api.pagination import Pagination
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import Application, User
from authentik.events.logs import LogEventSerializer, capture_logs
@@ -55,9 +57,21 @@ class ApplicationSerializer(ModelSerializer):
def get_launch_url(self, app: Application) -> str | None:
"""Allow formatting of launch URL"""
user = None
user_data = None
if "request" in self.context:
user = self.context["request"].user
return app.get_launch_url(user)
# Cache serialized user data to avoid N+1 when formatting launch URLs
# for multiple applications. UserSerializer accesses user.ak_groups which
# would otherwise trigger a query for each application.
if user is not None:
if "_cached_user_data" not in self.context:
# Prefetch groups to avoid N+1
self.context["_cached_user_data"] = UserSerializer(instance=user).data
user_data = self.context["_cached_user_data"]
return app.get_launch_url(user, user_data=user_data)
def validate_slug(self, slug: str) -> str:
if slug in Application.reserved_slugs:
@@ -150,8 +164,23 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
applications.append(application)
return applications
def _expand_applications(self, applications: list[Application]) -> QuerySet[Application]:
"""
Re-fetch with proper prefetching for serialization
Cached applications don't have prefetched relationships, causing N+1 queries
during serialization when get_provider() is called
"""
if not applications:
return self.get_queryset().none()
pks = [app.pk for app in applications]
return (
self.get_queryset()
.filter(pk__in=pks)
.order_by(Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(pks)]))
)
def _filter_applications_with_launch_url(
self, paginated_apps: Iterator[Application]
self, paginated_apps: QuerySet[Application]
) -> list[Application]:
applications = []
for app in paginated_apps:
@@ -254,6 +283,8 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
except ValueError as exc:
raise ValidationError from exc
allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user)
allowed_applications = self._expand_applications(allowed_applications)
serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data)
@@ -272,6 +303,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
allowed_applications,
timeout=86400,
)
allowed_applications = self._expand_applications(allowed_applications)
if only_with_launch_url == "true":
allowed_applications = self._filter_applications_with_launch_url(allowed_applications)

View File

@@ -78,7 +78,7 @@ class AdminDeviceViewSet(ViewSet):
"""Get all devices in all child classes"""
for model in device_classes():
device_set = get_objects_for_user(
self.request.user, f"{model._meta.app_label}.view_{model._meta.model_name}", model
self.request.user, f"{model._meta.app_label}.view_{model._meta.model_name}"
).filter(**kwargs)
yield from device_set

View File

@@ -18,10 +18,10 @@ from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import action
from rest_framework.fields import CharField, IntegerField, SerializerMethodField
from rest_framework.permissions import IsAuthenticated
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ListSerializer, ValidationError
from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet
from authentik.api.authentication import TokenAuthentication
@@ -33,6 +33,16 @@ from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.rbac.api.roles import RoleSerializer
from authentik.rbac.decorators import permission_required
PARTIAL_USER_SERIALIZER_MODEL_FIELDS = [
"pk",
"username",
"name",
"is_active",
"last_login",
"email",
"attributes",
]
class PartialUserSerializer(ModelSerializer):
"""Partial User Serializer, does not include child relations."""
@@ -42,20 +52,11 @@ class PartialUserSerializer(ModelSerializer):
class Meta:
model = User
fields = [
"pk",
"username",
"name",
"is_active",
"last_login",
"email",
"attributes",
"uid",
]
fields = PARTIAL_USER_SERIALIZER_MODEL_FIELDS + ["uid"]
class GroupChildSerializer(ModelSerializer):
"""Stripped down group serializer to show relevant children for groups"""
class RelatedGroupSerializer(ModelSerializer):
"""Stripped down group serializer to show relevant children/parents for groups"""
attributes = JSONDictField(required=False)
@@ -74,15 +75,16 @@ class GroupSerializer(ModelSerializer):
"""Group Serializer"""
attributes = JSONDictField(required=False)
users_obj = SerializerMethodField(allow_null=True)
parents = PrimaryKeyRelatedField(queryset=Group.objects.all(), many=True, required=False)
parents_obj = SerializerMethodField(allow_null=True)
children_obj = SerializerMethodField(allow_null=True)
users_obj = SerializerMethodField(allow_null=True)
roles_obj = ListSerializer(
child=RoleSerializer(),
read_only=True,
source="roles",
required=False,
)
parent_name = CharField(source="parent.name", read_only=True, allow_null=True)
num_pk = IntegerField(read_only=True)
@property
@@ -99,25 +101,30 @@ class GroupSerializer(ModelSerializer):
return True
return str(request.query_params.get("include_children", "false")).lower() == "true"
@property
def _should_include_parents(self) -> bool:
request: Request = self.context.get("request", None)
if not request:
return True
return str(request.query_params.get("include_parents", "false")).lower() == "true"
@extend_schema_field(PartialUserSerializer(many=True))
def get_users_obj(self, instance: Group) -> list[PartialUserSerializer] | None:
if not self._should_include_users:
return None
return PartialUserSerializer(instance.users, many=True).data
@extend_schema_field(GroupChildSerializer(many=True))
def get_children_obj(self, instance: Group) -> list[GroupChildSerializer] | None:
@extend_schema_field(RelatedGroupSerializer(many=True))
def get_children_obj(self, instance: Group) -> list[RelatedGroupSerializer] | None:
if not self._should_include_children:
return None
return GroupChildSerializer(instance.children, many=True).data
return RelatedGroupSerializer(instance.children, many=True).data
def validate_parent(self, parent: Group | None):
"""Validate group parent (if set), ensuring the parent isn't itself"""
if not self.instance or not parent:
return parent
if str(parent.group_uuid) == str(self.instance.group_uuid):
raise ValidationError(_("Cannot set group as parent of itself."))
return parent
@extend_schema_field(RelatedGroupSerializer(many=True))
def get_parents_obj(self, instance: Group) -> list[RelatedGroupSerializer] | None:
if not self._should_include_parents:
return None
return RelatedGroupSerializer(instance.parents, many=True).data
def validate_is_superuser(self, superuser: bool):
"""Ensure that the user creating this group has permissions to set the superuser flag"""
@@ -153,8 +160,8 @@ class GroupSerializer(ModelSerializer):
"num_pk",
"name",
"is_superuser",
"parent",
"parent_name",
"parents",
"parents_obj",
"users",
"users_obj",
"attributes",
@@ -171,9 +178,10 @@ class GroupSerializer(ModelSerializer):
"required": False,
"default": list,
},
# TODO: This field isn't unique on the database which is hard to backport
# hence we just validate the uniqueness here
"name": {"validators": [UniqueValidator(Group.objects.all())]},
"parents": {
"required": False,
"default": list,
},
}
@@ -248,14 +256,21 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
return [
StrField(Group, "name"),
BoolField(Group, "is_superuser", nullable=True),
JSONSearchField(Group, "attributes", suggest_nested=False),
JSONSearchField(Group, "attributes"),
]
def get_queryset(self):
base_qs = Group.objects.all().select_related("parent").prefetch_related("roles")
base_qs = Group.objects.all().prefetch_related("roles")
if self.serializer_class(context={"request": self.request})._should_include_users:
base_qs = base_qs.prefetch_related("users")
# Only fetch fields needed by PartialUserSerializer to reduce DB load and instantiation
# time
base_qs = base_qs.prefetch_related(
Prefetch(
"users",
queryset=User.objects.all().only(*PARTIAL_USER_SERIALIZER_MODEL_FIELDS),
)
)
else:
base_qs = base_qs.prefetch_related(
Prefetch("users", queryset=User.objects.all().only("id"))
@@ -264,12 +279,16 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
if self.serializer_class(context={"request": self.request})._should_include_children:
base_qs = base_qs.prefetch_related("children")
if self.serializer_class(context={"request": self.request})._should_include_parents:
base_qs = base_qs.prefetch_related("parents")
return base_qs
@extend_schema(
parameters=[
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
OpenApiParameter("include_parents", bool, default=False),
]
)
def list(self, request, *args, **kwargs):
@@ -279,6 +298,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
parameters=[
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
OpenApiParameter("include_parents", bool, default=False),
]
)
def retrieve(self, request, *args, **kwargs):

View File

@@ -4,7 +4,6 @@ from typing import Any
from django.utils.timezone import now
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import assign_perm, get_anonymous_user
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
@@ -145,19 +144,15 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
owner_field = "user"
rbac_allow_create_without_perm = True
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
if user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(user=user.pk)
def perform_create(self, serializer: TokenSerializer):
if not self.request.user.is_superuser:
instance = serializer.save(
user=self.request.user,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
)
assign_perm("authentik_core.view_token_key", self.request.user, instance)
self.request.user.assign_perms_to_managed_role(
"authentik_core.view_token_key", instance
)
return instance
return super().perform_create(serializer)

View File

@@ -81,7 +81,7 @@ class UsedByMixin:
# query and check if there is a difference between modes the user can see
# and can't see and add a warning
for obj in get_objects_for_user(
request.user, f"{app}.view_{model_name}", manager
request.user, f"{app}.view_{model_name}", manager.all()
).all():
# Only merge shadows on first object
if first_object:

View File

@@ -86,8 +86,10 @@ from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.lib.avatars import get_avatar
from authentik.lib.utils.reflection import ConditionalInheritance
from authentik.rbac.api.roles import RoleSerializer
from authentik.rbac.decorators import permission_required
from authentik.rbac.models import get_permission_choices
from authentik.rbac.models import Role, 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
@@ -106,7 +108,6 @@ class PartialGroupSerializer(ModelSerializer):
"""Partial Group Serializer, does not include child relations."""
attributes = JSONDictField(required=False)
parent_name = CharField(source="parent.name", read_only=True, allow_null=True)
class Meta:
model = Group
@@ -115,8 +116,6 @@ class PartialGroupSerializer(ModelSerializer):
"num_pk",
"name",
"is_superuser",
"parent",
"parent_name",
"attributes",
]
@@ -135,6 +134,13 @@ class UserSerializer(ModelSerializer):
default=list,
)
groups_obj = SerializerMethodField(allow_null=True)
roles = PrimaryKeyRelatedField(
allow_empty=True,
many=True,
queryset=Role.objects.all().order_by("name"),
default=list,
)
roles_obj = SerializerMethodField(allow_null=True)
uid = CharField(read_only=True)
username = CharField(
max_length=150,
@@ -148,12 +154,25 @@ class UserSerializer(ModelSerializer):
return True
return str(request.query_params.get("include_groups", "true")).lower() == "true"
@property
def _should_include_roles(self) -> bool:
request: Request = self.context.get("request", None)
if not request:
return True
return str(request.query_params.get("include_roles", "true")).lower() == "true"
@extend_schema_field(PartialGroupSerializer(many=True))
def get_groups_obj(self, instance: User) -> list[PartialGroupSerializer] | None:
if not self._should_include_groups:
return None
return PartialGroupSerializer(instance.ak_groups, many=True).data
@extend_schema_field(RoleSerializer(many=True))
def get_roles_obj(self, instance: User) -> list[RoleSerializer] | None:
if not self._should_include_roles:
return None
return RoleSerializer(instance.roles, many=True).data
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
@@ -168,24 +187,26 @@ class UserSerializer(ModelSerializer):
directly setting a password. However should be done via the `set_password`
method instead of directly setting it like rest_framework."""
password = validated_data.pop("password", None)
permissions = Permission.objects.filter(
perms_qs = Permission.objects.filter(
codename__in=[x.split(".")[1] for x in validated_data.pop("permissions", [])]
)
validated_data["user_permissions"] = permissions
).values_list("content_type__app_label", "codename")
perms_list = [f"{ct}.{name}" for ct, name in list(perms_qs)]
instance: User = super().create(validated_data)
self._set_password(instance, password)
instance.assign_perms_to_managed_role(perms_list)
return instance
def update(self, instance: User, validated_data: dict) -> User:
"""Same as `create` above, set the password directly if we're in a blueprint
context"""
password = validated_data.pop("password", None)
permissions = Permission.objects.filter(
perms_qs = Permission.objects.filter(
codename__in=[x.split(".")[1] for x in validated_data.pop("permissions", [])]
)
validated_data["user_permissions"] = permissions
).values_list("content_type__app_label", "codename")
perms_list = [f"{ct}.{name}" for ct, name in list(perms_qs)]
instance = super().update(instance, validated_data)
self._set_password(instance, password)
instance.assign_perms_to_managed_role(perms_list)
return instance
def _set_password(self, instance: User, password: str | None):
@@ -240,6 +261,8 @@ class UserSerializer(ModelSerializer):
"is_superuser",
"groups",
"groups_obj",
"roles",
"roles_obj",
"email",
"avatar",
"attributes",
@@ -263,6 +286,7 @@ class UserSelfSerializer(ModelSerializer):
is_superuser = BooleanField(read_only=True)
avatar = SerializerMethodField()
groups = SerializerMethodField()
roles = SerializerMethodField()
uid = CharField(read_only=True)
settings = SerializerMethodField()
system_permissions = SerializerMethodField()
@@ -290,6 +314,25 @@ class UserSelfSerializer(ModelSerializer):
"pk": group.pk,
}
@extend_schema_field(
ListSerializer(
child=inline_serializer(
"UserSelfRoles",
{
"name": CharField(read_only=True),
"pk": CharField(read_only=True),
},
)
)
)
def get_roles(self, _: User):
"""Return only the roles a user is member of"""
for role in self.instance.all_roles().order_by("name"):
yield {
"name": role.name,
"pk": role.pk,
}
def get_settings(self, user: User) -> dict[str, Any]:
"""Get user settings with brand and group settings applied"""
return user.group_attributes(self._context["request"]).get("settings", {})
@@ -311,6 +354,7 @@ class UserSelfSerializer(ModelSerializer):
"is_active",
"is_superuser",
"groups",
"roles",
"email",
"avatar",
"uid",
@@ -390,6 +434,16 @@ class UsersFilter(FilterSet):
queryset=Group.objects.all().order_by("name"),
)
roles_by_name = ModelMultipleChoiceFilter(
field_name="roles__name",
to_field_name="name",
queryset=Role.objects.all().order_by("name"),
)
roles_by_pk = ModelMultipleChoiceFilter(
field_name="roles",
queryset=Role.objects.all().order_by("name"),
)
def filter_is_superuser(self, queryset, name, value):
if value:
return queryset.filter(ak_groups__is_superuser=True).distinct()
@@ -425,11 +479,17 @@ class UsersFilter(FilterSet):
"attributes",
"groups_by_name",
"groups_by_pk",
"roles_by_name",
"roles_by_pk",
"type",
]
class UserViewSet(UsedByMixin, ModelViewSet):
class UserViewSet(
ConditionalInheritance("authentik.enterprise.reports.api.reports.ExportMixin"),
UsedByMixin,
ModelViewSet,
):
"""User Viewset"""
queryset = User.objects.none()
@@ -458,18 +518,21 @@ class UserViewSet(UsedByMixin, ModelViewSet):
StrField(User, "path"),
BoolField(User, "is_active", nullable=True),
ChoiceSearchField(User, "type"),
JSONSearchField(User, "attributes", suggest_nested=False),
JSONSearchField(User, "attributes"),
]
def get_queryset(self):
base_qs = User.objects.all().exclude_anonymous()
if self.serializer_class(context={"request": self.request})._should_include_groups:
base_qs = base_qs.prefetch_related("ak_groups")
if self.serializer_class(context={"request": self.request})._should_include_roles:
base_qs = base_qs.prefetch_related("roles")
return base_qs
@extend_schema(
parameters=[
OpenApiParameter("include_groups", bool, default=True),
OpenApiParameter("include_roles", bool, default=True),
]
)
def list(self, request, *args, **kwargs):

View File

@@ -12,7 +12,27 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
class InbuiltBackend(ModelBackend):
class ModelBackendNoAuthz(ModelBackend):
def get_user_permissions(self, user_obj, obj=None):
return set()
def get_group_permissions(self, user_obj, obj=None):
return set()
def get_all_permissions(self, user_obj, obj=None):
return set()
def has_perm(self, user_obj, perm, obj=None):
return False
def has_module_perms(self, user_obj, app_label):
return False
def with_perm(self, perm, is_active=True, include_superusers=True, obj=None):
return User.objects.none()
class InbuiltBackend(ModelBackendNoAuthz):
"""Inbuilt backend"""
def authenticate(

View File

@@ -6,7 +6,6 @@ import django.contrib.auth.models
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
import guardian.mixins
from django.conf import settings
from django.db import migrations, models
@@ -111,7 +110,7 @@ class Migration(migrations.Migration):
options={
"permissions": (("reset_user_password", "Reset Password"),),
},
bases=(guardian.mixins.GuardianUserMixin, models.Model),
bases=(models.Model,),
managers=[
("objects", django.contrib.auth.models.UserManager()),
],

View File

@@ -0,0 +1,155 @@
# Generated by Django 5.1.12 on 2025-09-12 08:38
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
import psqlextra.backend.migrations.operations.apply_state
import psqlextra.backend.migrations.operations.create_materialized_view_model
import psqlextra.indexes.unique_index
import psqlextra.manager.manager
import psqlextra.models.view
import uuid
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def migrate_parents(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Group = apps.get_model("authentik_core", "Group")
db_alias = schema_editor.connection.alias
for group in Group.objects.using(db_alias).all():
if not group.parent:
continue
group.parents.add(group.parent)
group.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0054_alter_application_meta_icon_alter_source_icon"),
]
operations = [
migrations.CreateModel(
name="GroupParentageNode",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
],
options={
"verbose_name": "Group Parentage Node",
"verbose_name_plural": "Group Parentage Nodes",
"db_table": "authentik_core_groupparentage",
},
),
migrations.AddField(
model_name="groupparentagenode",
name="child",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="parent_nodes",
to="authentik_core.group",
),
),
migrations.AddField(
model_name="groupparentagenode",
name="parent",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="child_nodes",
to="authentik_core.group",
),
),
psqlextra.backend.migrations.operations.create_materialized_view_model.PostgresCreateMaterializedViewModel(
name="GroupAncestryNode",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
],
options={
"db_table": "authentik_core_groupancestry",
},
view_options={
"query": (
"\n WITH RECURSIVE accumulator AS (\n SELECT\n child_id::text || '-' || parent_id::text as id,\n child_id AS descendant_id,\n parent_id AS ancestor_id\n FROM authentik_core_groupparentage\n\n UNION\n\n SELECT\n accumulator.descendant_id::text || '-' || current.parent_id::text as id,\n accumulator.descendant_id,\n current.parent_id AS ancestor_id\n FROM accumulator\n JOIN authentik_core_groupparentage current\n ON accumulator.ancestor_id = current.child_id\n )\n SELECT * FROM accumulator\n ",
(),
),
},
bases=(psqlextra.models.view.PostgresMaterializedViewModel,),
managers=[
("objects", psqlextra.manager.manager.PostgresManager()),
],
),
psqlextra.backend.migrations.operations.apply_state.ApplyState(
state_operation=migrations.AddField(
model_name="groupancestrynode",
name="ancestor",
field=models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="descendant_nodes",
to="authentik_core.group",
),
),
),
psqlextra.backend.migrations.operations.apply_state.ApplyState(
state_operation=migrations.AddField(
model_name="groupancestrynode",
name="descendant",
field=models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="ancestor_nodes",
to="authentik_core.group",
),
),
),
migrations.AddIndex(
model_name="groupancestrynode",
index=models.Index(fields=["descendant"], name="authentik_c_descend_f83a71_idx"),
),
migrations.AddIndex(
model_name="groupancestrynode",
index=models.Index(fields=["ancestor"], name="authentik_c_ancesto_974845_idx"),
),
migrations.AddIndex(
model_name="groupancestrynode",
index=psqlextra.indexes.unique_index.UniqueIndex(
fields=["id"], name="authentik_c_id_5d0bb4_idx"
),
),
pgtrigger.migrations.AddTrigger(
model_name="groupparentagenode",
trigger=pgtrigger.compiler.Trigger(
name="refresh_groupancestry",
sql=pgtrigger.compiler.UpsertTriggerSql(
func="\n REFRESH MATERIALIZED VIEW CONCURRENTLY authentik_core_groupancestry;\n RETURN NULL;\n ",
hash="a987621714359aa0389e03fd2d52f86b118e7d24",
operation="INSERT OR UPDATE OR DELETE",
pgid="pgtrigger_refresh_groupancestry_62450",
table="authentik_core_groupparentage",
when="AFTER",
),
),
),
migrations.AddField(
model_name="group",
name="parents",
field=models.ManyToManyField(
blank=True,
related_name="children",
through="authentik_core.GroupParentageNode",
to="authentik_core.group",
),
),
migrations.RunPython(migrate_parents, migrations.RunPython.noop),
]

View File

@@ -0,0 +1,178 @@
# Generated by Django 5.1.12 on 2025-09-30 12:29
from django.db import migrations, models
from django.apps.registry import Apps
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def migrate_object_permissions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
User = apps.get_model("authentik_core", "User")
Group = apps.get_model("auth", "Group")
Role = apps.get_model("authentik_rbac", "Role")
UserObjectPermission = apps.get_model("guardian", "UserObjectPermission")
GroupObjectPermission = apps.get_model("guardian", "GroupObjectPermission")
RoleObjectPermission = apps.get_model("guardian", "RoleObjectPermission")
RoleModelPermission = apps.get_model("guardian", "RoleModelPermission")
def get_role_for_user_id(user_id: int) -> Role:
name = f"ak-migrated-role--user-{user_id}"
role, created = Role.objects.using(db_alias).get_or_create(
name=name,
)
if created:
role.users.add(user_id)
return role
def get_role_for_group_id(group_id: int) -> Role:
role = Role.objects.using(db_alias).filter(group_id=group_id).first()
if not role:
# Every django group should already have a role, so this should never happen.
# But let's be nice.
name = f"ak-migrated-role--group-{group_id}"
role, created = Role.objects.using(db_alias).get_or_create(
group_id=group_id,
name=name,
)
if created:
role.group_id = group_id
role.save()
return role
# Below are 4 very similar pieces of code, for (user, group) x (model, object).
# Since this is a one-off migration, I won't attempt DRYing them.
# User model permissions
user_ids_with_model_permissions = (
User.user_permissions.through.objects.using(db_alias)
.values_list("user", flat=True)
.distinct()
)
for user_id in user_ids_with_model_permissions:
role = get_role_for_user_id(user_id)
user_model_permissions = User.user_permissions.through.objects.using(db_alias).filter(
user_id=user_id
)
role_model_permissions = []
for user_model_permission in user_model_permissions:
role_model_permissions.append(
RoleModelPermission(
permission=user_model_permission.permission,
content_type=user_model_permission.permission.content_type,
role=role,
)
)
RoleModelPermission.objects.using(db_alias).bulk_create(role_model_permissions)
# Group model permissions
group_ids_with_model_permissions = (
Group.permissions.through.objects.using(db_alias).values_list("group", flat=True).distinct()
)
for group_id in group_ids_with_model_permissions:
role = get_role_for_group_id(group_id)
group_model_permissions = Group.permissions.through.objects.using(db_alias).filter(
group_id=group_id
)
role_model_permissions = []
for group_model_permission in group_model_permissions:
role_model_permissions.append(
RoleModelPermission(
permission=group_model_permission.permission,
content_type=group_model_permission.permission.content_type,
role=role,
)
)
RoleModelPermission.objects.using(db_alias).bulk_create(role_model_permissions)
# User object permissions
user_ids_with_object_permissions = (
UserObjectPermission.objects.using(db_alias).values_list("user", flat=True).distinct()
)
for user_id in user_ids_with_object_permissions:
role = get_role_for_user_id(user_id)
user_object_permissions = UserObjectPermission.objects.using(db_alias).filter(user=user_id)
role_object_permissions = []
for user_object_permission in user_object_permissions:
role_object_permissions.append(
RoleObjectPermission(
permission=user_object_permission.permission,
content_type=user_object_permission.content_type,
object_pk=user_object_permission.object_pk,
role=role,
)
)
RoleObjectPermission.objects.using(db_alias).bulk_create(role_object_permissions)
# Group object permissions
group_ids_with_object_permissions = (
GroupObjectPermission.objects.using(db_alias).values_list("group", flat=True).distinct()
)
for group_id in group_ids_with_object_permissions:
role = get_role_for_group_id(group_id)
group_object_permissions = GroupObjectPermission.objects.using(db_alias).filter(
group=group_id
)
role_object_permissions = []
for group_object_permission in group_object_permissions:
role_object_permissions.append(
RoleObjectPermission(
permission=group_object_permission.permission,
content_type=group_object_permission.content_type,
object_pk=group_object_permission.object_pk,
role=role,
)
)
RoleObjectPermission.objects.using(db_alias).bulk_create(role_object_permissions)
class Migration(migrations.Migration):
dependencies = [
("guardian", "0004_role_permissions"),
("authentik_core", "0055_groupancestor_groupparentagenode_group_parents"),
("authentik_rbac", "0008_alter_role_group"),
]
operations = [
migrations.AddField(
model_name="user",
name="roles",
field=models.ManyToManyField(
blank=True, related_name="users", to="authentik_rbac.role"
),
),
migrations.RunPython(migrate_object_permissions),
migrations.AlterUniqueTogether(
name="group",
unique_together=set(),
),
migrations.AlterField(
model_name="group",
name="parents",
field=models.ManyToManyField(
blank=True,
related_name="children",
through="authentik_core.GroupParentageNode",
to="authentik_core.group",
),
),
migrations.RemoveField(
model_name="group",
name="parent",
),
migrations.AlterField(
model_name="group",
name="name",
field=models.TextField(unique=True, verbose_name="name"),
),
]

View File

@@ -6,9 +6,10 @@ from hashlib import sha256
from typing import Any, Optional, Self
from uuid import uuid4
import pgtrigger
from deepmerge import always_merger
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import AbstractUser, Permission
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.contrib.sessions.base_session import AbstractBaseSession
from django.core.validators import validate_slug
@@ -19,10 +20,11 @@ from django.http import HttpRequest
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_cte import CTE, with_cte
from guardian.conf import settings
from guardian.mixins import GuardianUserMixin
from guardian.models import RoleModelPermission, RoleObjectPermission
from model_utils.managers import InheritanceManager
from psqlextra.indexes import UniqueIndex
from psqlextra.models import PostgresMaterializedViewModel
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
@@ -43,6 +45,7 @@ from authentik.lib.models import (
)
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel
from authentik.rbac.models import Role
from authentik.tenants.models import DEFAULT_TOKEN_DURATION, DEFAULT_TOKEN_LENGTH
from authentik.tenants.utils import get_current_tenant, get_unique_identifier
@@ -69,6 +72,17 @@ options.DEFAULT_NAMES = options.DEFAULT_NAMES + (
GROUP_RECURSION_LIMIT = 20
MANAGED_ROLE_PREFIX_USER = "ak-managed-role--user"
MANAGED_ROLE_PREFIX_GROUP = "ak-managed-role--group"
def managed_role_name(user_or_group: models.Model):
if isinstance(user_or_group, User):
return f"{MANAGED_ROLE_PREFIX_USER}-{user_or_group.pk}"
if isinstance(user_or_group, Group):
return f"{MANAGED_ROLE_PREFIX_GROUP}-{user_or_group.pk}"
raise TypeError("Managed roles are only available for User or Group.")
def default_token_duration() -> datetime:
"""Default duration a Token is valid"""
@@ -138,7 +152,7 @@ class AttributesMixin(models.Model):
@classmethod
def update_or_create_attributes(
cls, query: dict[str, Any], properties: dict[str, Any]
) -> tuple[models.Model, bool]:
) -> tuple[Self, bool]:
"""Same as django's update_or_create but correctly updates attributes by merging dicts"""
instance = cls.objects.filter(**query).first()
if not instance:
@@ -148,69 +162,40 @@ class AttributesMixin(models.Model):
class GroupQuerySet(QuerySet):
def with_children_recursive(self):
"""Recursively get all groups that have the current queryset as parents
or are indirectly related."""
def with_descendants(self):
pks = self.values_list("pk", flat=True)
return Group.objects.filter(Q(pk__in=pks) | Q(ancestor_nodes__ancestor__in=pks)).distinct()
def make_cte(cte):
"""Build the query that ends up in WITH RECURSIVE"""
# Start from self, aka the current query
# Add a depth attribute to limit the recursion
return self.annotate(
relative_depth=models.Value(0, output_field=models.IntegerField())
).union(
# Here is the recursive part of the query. cte refers to the previous iteration
# Only select groups for which the parent is part of the previous iteration
# and increase the depth
# Finally, limit the depth
cte.join(Group, group_uuid=cte.col.parent_id)
.annotate(
relative_depth=models.ExpressionWrapper(
cte.col.relative_depth
+ models.Value(1, output_field=models.IntegerField()),
output_field=models.IntegerField(),
)
)
.filter(relative_depth__lt=GROUP_RECURSION_LIMIT),
all=True,
)
# Build the recursive query, see above
cte = CTE.recursive(make_cte)
# Return the result, as a usable queryset for Group.
return with_cte(cte, select=cte.join(Group, group_uuid=cte.col.group_uuid))
def with_ancestors(self):
pks = self.values_list("pk", flat=True)
return Group.objects.filter(
Q(pk__in=pks) | Q(descendant_nodes__descendant__in=pks)
).distinct()
class Group(SerializerModel, AttributesMixin):
"""Group model which supports a basic hierarchy and has attributes"""
"""Group model which supports a hierarchy and has attributes"""
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField(_("name"))
name = models.TextField(verbose_name=_("name"), unique=True)
is_superuser = models.BooleanField(
default=False, help_text=_("Users added to this group will be superusers.")
)
roles = models.ManyToManyField("authentik_rbac.Role", related_name="ak_groups", blank=True)
parent = models.ForeignKey(
parents = models.ManyToManyField(
"Group",
blank=True,
null=True,
default=None,
on_delete=models.SET_NULL,
symmetrical=False,
through="GroupParentageNode",
related_name="children",
)
objects = GroupQuerySet.as_manager()
class Meta:
unique_together = (
(
"name",
"parent",
),
)
indexes = (
models.Index(fields=["name"]),
models.Index(fields=["is_superuser"]),
@@ -244,12 +229,103 @@ class Group(SerializerModel, AttributesMixin):
"""Recursively check if `user` is member of us, or any parent."""
return user.all_groups().filter(group_uuid=self.group_uuid).exists()
def children_recursive(self: Self | QuerySet["Group"]) -> QuerySet["Group"]:
"""Compatibility layer for Group.objects.with_children_recursive()"""
qs = self
if not isinstance(self, QuerySet):
qs = Group.objects.filter(group_uuid=self.group_uuid)
return qs.with_children_recursive()
def all_roles(self) -> QuerySet[Role]:
"""Get all roles of this group and all of its ancestors."""
return Role.objects.filter(
ak_groups__in=Group.objects.filter(pk=self.pk).with_ancestors()
).distinct()
def get_managed_role(self, create=False):
if create:
name = managed_role_name(self)
role, created = Role.objects.get_or_create(name=name, managed=name)
if created:
role.ak_groups.add(self)
return role
else:
return Role.objects.filter(name=managed_role_name(self)).first()
def assign_perms_to_managed_role(
self,
perms: str | list[str] | Permission | list[Permission],
obj: models.Model | None = None,
):
if not perms:
return
role = self.get_managed_role(create=True)
role.assign_perms(perms, obj)
class GroupParentageNode(models.Model):
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
child = models.ForeignKey(Group, related_name="parent_nodes", on_delete=models.CASCADE)
parent = models.ForeignKey(Group, related_name="child_nodes", on_delete=models.CASCADE)
class Meta:
verbose_name = _("Group Parentage Node")
verbose_name_plural = _("Group Parentage Nodes")
db_table = "authentik_core_groupparentage"
triggers = [
pgtrigger.Trigger(
name="refresh_groupancestry",
operation=pgtrigger.Insert | pgtrigger.Update | pgtrigger.Delete,
when=pgtrigger.After,
func="""
REFRESH MATERIALIZED VIEW CONCURRENTLY authentik_core_groupancestry;
RETURN NULL;
""",
),
]
def __str__(self) -> str:
return f"Group Parentage Node from #{self.child_id} to {self.parent_id}"
class GroupAncestryNode(PostgresMaterializedViewModel):
descendant = models.ForeignKey(
Group, related_name="ancestor_nodes", on_delete=models.DO_NOTHING
)
ancestor = models.ForeignKey(
Group, related_name="descendant_nodes", on_delete=models.DO_NOTHING
)
class Meta:
# This is a transitive closure of authentik_core_groupparentage
# See https://en.wikipedia.org/wiki/Transitive_closure#In_graph_theory
db_table = "authentik_core_groupancestry"
indexes = [
models.Index(fields=["descendant"]),
models.Index(fields=["ancestor"]),
UniqueIndex(fields=["id"]),
]
class ViewMeta:
query = """
WITH RECURSIVE accumulator AS (
SELECT
child_id::text || '-' || parent_id::text as id,
child_id AS descendant_id,
parent_id AS ancestor_id
FROM authentik_core_groupparentage
UNION
SELECT
accumulator.descendant_id::text || '-' || current.parent_id::text as id,
accumulator.descendant_id,
current.parent_id AS ancestor_id
FROM accumulator
JOIN authentik_core_groupparentage current
ON accumulator.ancestor_id = current.child_id
)
SELECT * FROM accumulator
"""
def __str__(self) -> str:
return f"Group Ancestry Node from {self.descendant_id} to {self.ancestor_id}"
class UserQuerySet(models.QuerySet):
@@ -276,7 +352,7 @@ class UserManager(DjangoUserManager):
return self.get_queryset().exclude_anonymous()
class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
class User(SerializerModel, AttributesMixin, AbstractUser):
"""authentik User model, based on django's contrib auth user model."""
uuid = models.UUIDField(default=uuid4, editable=False, unique=True)
@@ -286,6 +362,7 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
sources = models.ManyToManyField("Source", through="UserSourceConnection")
ak_groups = models.ManyToManyField("Group", related_name="users")
roles = models.ManyToManyField("authentik_rbac.Role", related_name="users", blank=True)
password_change_date = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
@@ -323,7 +400,60 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
def all_groups(self) -> QuerySet[Group]:
"""Recursively get all groups this user is a member of."""
return self.ak_groups.all().with_children_recursive()
return self.ak_groups.all().with_ancestors()
def all_roles(self) -> QuerySet[Role]:
"""Get all roles of this user and all of its groups (recursively)."""
return Role.objects.filter(Q(users=self) | Q(ak_groups__in=self.all_groups())).distinct()
def get_managed_role(self, create=False):
if create:
name = managed_role_name(self)
role, created = Role.objects.get_or_create(name=name, managed=name)
if created:
role.users.add(self)
return role
else:
return Role.objects.filter(name=managed_role_name(self)).first()
def get_all_model_perms_on_managed_role(self) -> QuerySet[RoleModelPermission]:
role = self.get_managed_role()
if not role:
return RoleModelPermission.objects.none()
return RoleModelPermission.objects.filter(role=role)
def get_all_obj_perms_on_managed_role(self) -> QuerySet[RoleObjectPermission]:
role = self.get_managed_role()
if not role:
return RoleObjectPermission.objects.none()
return RoleObjectPermission.objects.filter(role=role)
def assign_perms_to_managed_role(
self,
perms: str | list[str] | Permission | list[Permission],
obj: models.Model | None = None,
):
if not perms:
return
role = self.get_managed_role(create=True)
role.assign_perms(perms, obj)
def remove_perms_from_managed_role(
self,
perms: str | list[str] | Permission | list[Permission],
obj: models.Model | None = None,
):
role = self.get_managed_role()
if not role:
return None
role.remove_perms(perms, obj)
def remove_all_perms_from_managed_role(self):
role = self.get_managed_role()
if not role:
return None
RoleModelPermission.objects.filter(role=role).delete()
RoleObjectPermission.objects.filter(role=role).delete()
def group_attributes(self, request: HttpRequest | None = None) -> dict[str, Any]:
"""Get a dictionary containing the attributes from all groups the user belongs to,
@@ -528,6 +658,10 @@ class ApplicationQuerySet(QuerySet):
qs = self.select_related("provider")
for subclass in Provider.objects.get_queryset()._get_subclasses_recurse(Provider):
qs = qs.select_related(f"provider__{subclass}")
# Also prefetch/select through each subclass path to ensure casted instances have access
qs = qs.prefetch_related(f"provider__{subclass}__property_mappings")
qs = qs.select_related(f"provider__{subclass}__application")
qs = qs.select_related(f"provider__{subclass}__backchannel_application")
return qs
@@ -579,8 +713,15 @@ class Application(SerializerModel, PolicyBindingModel):
return get_file_manager(FileUsage.MEDIA).file_url(self.meta_icon)
def get_launch_url(self, user: Optional["User"] = None) -> str | None:
"""Get launch URL if set, otherwise attempt to get launch URL based on provider."""
def get_launch_url(
self, user: Optional["User"] = None, user_data: dict | None = None
) -> str | None:
"""Get launch URL if set, otherwise attempt to get launch URL based on provider.
Args:
user: User instance for formatting the URL
user_data: Pre-serialized user data to avoid re-serialization (performance optimization)
"""
from authentik.core.api.users import UserSerializer
url = None
@@ -590,7 +731,10 @@ class Application(SerializerModel, PolicyBindingModel):
url = provider.launch_url
if user and url:
try:
return url % UserSerializer(instance=user).data
# Use pre-serialized data if available, otherwise serialize now
if user_data is None:
user_data = UserSerializer(instance=user).data
return url % user_data
except Exception as exc: # noqa
LOGGER.warning("Failed to format launch url", exc=exc)
return url

View File

@@ -34,19 +34,12 @@ class SessionStore(SessionBase):
def _get_session_from_db(self):
try:
return (
self.model.objects.select_related(
"authenticatedsession",
"authenticatedsession__user",
)
.prefetch_related(
"authenticatedsession__user__groups",
"authenticatedsession__user__user_permissions",
)
.get(
session_key=self.session_key,
expires__gt=timezone.now(),
)
return self.model.objects.select_related(
"authenticatedsession",
"authenticatedsession__user",
).get(
session_key=self.session_key,
expires__gt=timezone.now(),
)
except (self.model.DoesNotExist, SuspiciousOperation) as exc:
if isinstance(exc, SuspiciousOperation):
@@ -55,19 +48,12 @@ class SessionStore(SessionBase):
async def _aget_session_from_db(self):
try:
return (
await self.model.objects.select_related(
"authenticatedsession",
"authenticatedsession__user",
)
.prefetch_related(
"authenticatedsession__user__groups",
"authenticatedsession__user__user_permissions",
)
.aget(
session_key=self.session_key,
expires__gt=timezone.now(),
)
return await self.model.objects.select_related(
"authenticatedsession",
"authenticatedsession__user",
).aget(
session_key=self.session_key,
expires__gt=timezone.now(),
)
except (self.model.DoesNotExist, SuspiciousOperation) as exc:
if isinstance(exc, SuspiciousOperation):
@@ -80,9 +66,12 @@ class SessionStore(SessionBase):
def decode(self, session_data):
try:
return pickle.loads(session_data) # nosec
except pickle.PickleError:
# ValueError, unpickling exceptions. If any of these happen, just return an empty
# dictionary (an empty session)
except (pickle.PickleError, AttributeError, TypeError):
# PickleError, ValueError - unpickling exceptions
# AttributeError - can happen when Django model fields (e.g., FileField) are unpickled
# and their descriptors fail to initialize (e.g., missing storage)
# TypeError - can happen with incompatible pickled objects
# If any of these happen, just return an empty dictionary (an empty session)
pass
return {}

View File

@@ -35,8 +35,13 @@ def clean_expired_models():
LOGGER.debug("Expired models", model=cls, amount=amount)
self.info(f"Expired {amount} {cls._meta.verbose_name_plural}")
clear_expired_cache()
Message.delete_expired()
GroupChannel.delete_expired()
for cls in [Message, GroupChannel]:
objects = cls.objects.all().filter(expires__lt=now())
amount = objects.count()
for obj in chunked_queryset(objects):
obj.delete()
LOGGER.debug("Expired models", model=cls, amount=amount)
self.info(f"Expired {amount} {cls._meta.verbose_name_plural}")
@actor(description=_("Remove temporary users created by SAML Sources."))

View File

@@ -1,7 +1,6 @@
"""Test Application Entitlements API"""
from django.urls import reverse
from guardian.shortcuts import assign_perm
from rest_framework.test import APITestCase
from authentik.core.models import Application, ApplicationEntitlement, Group
@@ -49,7 +48,8 @@ class TestApplicationEntitlements(APITestCase):
def test_group_indirect(self):
"""Test indirect group"""
parent = Group.objects.create(name=generate_id())
group = Group.objects.create(name=generate_id(), parent=parent)
group = Group.objects.create(name=generate_id())
group.parents.add(parent)
self.user.ak_groups.add(group)
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
PolicyBinding.objects.create(target=ent, group=parent, order=0)
@@ -76,8 +76,8 @@ class TestApplicationEntitlements(APITestCase):
def test_api_perms_global(self):
"""Test API creation with global permissions"""
assign_perm("authentik_core.add_applicationentitlement", self.user)
assign_perm("authentik_core.view_application", self.user)
self.user.assign_perms_to_managed_role("authentik_core.add_applicationentitlement")
self.user.assign_perms_to_managed_role("authentik_core.view_application")
self.client.force_login(self.user)
res = self.client.post(
reverse("authentik_api:applicationentitlement-list"),
@@ -90,8 +90,8 @@ class TestApplicationEntitlements(APITestCase):
def test_api_perms_scoped(self):
"""Test API creation with scoped permissions"""
assign_perm("authentik_core.add_applicationentitlement", self.user)
assign_perm("authentik_core.view_application", self.user, self.app)
self.user.assign_perms_to_managed_role("authentik_core.add_applicationentitlement")
self.user.assign_perms_to_managed_role("authentik_core.view_application", self.app)
self.client.force_login(self.user)
res = self.client.post(
reverse("authentik_api:applicationentitlement-list"),
@@ -104,7 +104,7 @@ class TestApplicationEntitlements(APITestCase):
def test_api_perms_missing(self):
"""Test API creation with no permissions"""
assign_perm("authentik_core.add_applicationentitlement", self.user)
self.user.assign_perms_to_managed_role("authentik_core.add_applicationentitlement")
self.client.force_login(self.user)
res = self.client.post(
reverse("authentik_api:applicationentitlement-list"),

View File

@@ -25,7 +25,8 @@ class TestGroups(TestCase):
user = User.objects.create(username=generate_id())
user2 = User.objects.create(username=generate_id())
parent = Group.objects.create(name=generate_id())
child = Group.objects.create(name=generate_id(), parent=parent)
child = Group.objects.create(name=generate_id())
child.parents.add(parent)
child.users.add(user)
self.assertTrue(child.is_member(user))
self.assertTrue(parent.is_member(user))
@@ -37,8 +38,10 @@ class TestGroups(TestCase):
user = User.objects.create(username=generate_id())
user2 = User.objects.create(username=generate_id())
parent = Group.objects.create(name=generate_id())
second = Group.objects.create(name=generate_id(), parent=parent)
third = Group.objects.create(name=generate_id(), parent=second)
second = Group.objects.create(name=generate_id())
second.parents.add(parent)
third = Group.objects.create(name=generate_id())
third.parents.add(second)
second.users.add(user)
self.assertTrue(parent.is_member(user))
self.assertFalse(parent.is_member(user2))
@@ -51,9 +54,21 @@ class TestGroups(TestCase):
"""Test group membership (recursive)"""
user = User.objects.create(username=generate_id())
group = Group.objects.create(name=generate_id())
group2 = Group.objects.create(name=generate_id(), parent=group)
group2 = Group.objects.create(name=generate_id())
group.parents.add(group2)
group2.parents.add(group)
group.users.add(user)
group.parent = group2
group.save()
self.assertTrue(group.is_member(user))
self.assertTrue(group2.is_member(user))
def test_group_managed_role(self):
"""Test group managed role"""
perm = "authentik_core.view_user"
user = User.objects.create(username=generate_id())
group = Group.objects.create(name=generate_id())
group.users.add(user)
group.assign_perms_to_managed_role(perm)
self.assertEqual(group.roles.count(), 1)
self.assertEqual(user.roles.count(), 0)
self.assertTrue(user.has_perm(perm))

View File

@@ -1,7 +1,6 @@
"""Test Groups API"""
from django.urls.base import reverse
from guardian.shortcuts import assign_perm
from rest_framework.test import APITestCase
from authentik.core.models import Group
@@ -37,8 +36,8 @@ class TestGroupsAPI(APITestCase):
def test_add_user(self):
"""Test add_user"""
group = Group.objects.create(name=generate_id())
assign_perm("authentik_core.add_user_to_group", self.login_user, group)
assign_perm("authentik_core.view_user", self.login_user)
self.login_user.assign_perms_to_managed_role("authentik_core.add_user_to_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.view_user")
self.client.force_login(self.login_user)
res = self.client.post(
reverse("authentik_api:group-add-user", kwargs={"pk": group.pk}),
@@ -53,8 +52,8 @@ class TestGroupsAPI(APITestCase):
def test_add_user_404(self):
"""Test add_user"""
group = Group.objects.create(name=generate_id())
assign_perm("authentik_core.add_user_to_group", self.login_user, group)
assign_perm("authentik_core.view_user", self.login_user)
self.login_user.assign_perms_to_managed_role("authentik_core.add_user_to_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.view_user")
self.client.force_login(self.login_user)
res = self.client.post(
reverse("authentik_api:group-add-user", kwargs={"pk": group.pk}),
@@ -67,8 +66,8 @@ class TestGroupsAPI(APITestCase):
def test_remove_user(self):
"""Test remove_user"""
group = Group.objects.create(name=generate_id())
assign_perm("authentik_core.remove_user_from_group", self.login_user, group)
assign_perm("authentik_core.view_user", self.login_user)
self.login_user.assign_perms_to_managed_role("authentik_core.remove_user_from_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.view_user")
group.users.add(self.user)
self.client.force_login(self.login_user)
res = self.client.post(
@@ -84,8 +83,8 @@ class TestGroupsAPI(APITestCase):
def test_remove_user_404(self):
"""Test remove_user"""
group = Group.objects.create(name=generate_id())
assign_perm("authentik_core.remove_user_from_group", self.login_user, group)
assign_perm("authentik_core.view_user", self.login_user)
self.login_user.assign_perms_to_managed_role("authentik_core.remove_user_from_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.view_user")
group.users.add(self.user)
self.client.force_login(self.login_user)
res = self.client.post(
@@ -96,23 +95,9 @@ class TestGroupsAPI(APITestCase):
)
self.assertEqual(res.status_code, 404)
def test_parent_self(self):
"""Test parent"""
group = Group.objects.create(name=generate_id())
assign_perm("view_group", self.login_user, group)
assign_perm("change_group", self.login_user, group)
self.client.force_login(self.login_user)
res = self.client.patch(
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
data={
"parent": group.pk,
},
)
self.assertEqual(res.status_code, 400)
def test_superuser_no_perm(self):
"""Test creating a superuser group without permission"""
assign_perm("authentik_core.add_group", self.login_user)
self.login_user.assign_perms_to_managed_role("authentik_core.add_group")
self.client.force_login(self.login_user)
res = self.client.post(
reverse("authentik_api:group-list"),
@@ -126,7 +111,7 @@ class TestGroupsAPI(APITestCase):
def test_superuser_no_perm_no_superuser(self):
"""Test creating a group without permission and without superuser flag"""
assign_perm("authentik_core.add_group", self.login_user)
self.login_user.assign_perms_to_managed_role("authentik_core.add_group")
self.client.force_login(self.login_user)
res = self.client.post(
reverse("authentik_api:group-list"),
@@ -137,8 +122,8 @@ class TestGroupsAPI(APITestCase):
def test_superuser_update_no_perm(self):
"""Test updating a superuser group without permission"""
group = Group.objects.create(name=generate_id(), is_superuser=True)
assign_perm("view_group", self.login_user, group)
assign_perm("change_group", self.login_user, group)
self.login_user.assign_perms_to_managed_role("view_group", group)
self.login_user.assign_perms_to_managed_role("change_group", group)
self.client.force_login(self.login_user)
res = self.client.patch(
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
@@ -154,8 +139,8 @@ class TestGroupsAPI(APITestCase):
"""Test updating a superuser group without permission
and without changing the superuser status"""
group = Group.objects.create(name=generate_id(), is_superuser=True)
assign_perm("view_group", self.login_user, group)
assign_perm("change_group", self.login_user, group)
self.login_user.assign_perms_to_managed_role("view_group", group)
self.login_user.assign_perms_to_managed_role("change_group", group)
self.client.force_login(self.login_user)
res = self.client.patch(
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
@@ -165,8 +150,8 @@ class TestGroupsAPI(APITestCase):
def test_superuser_create(self):
"""Test creating a superuser group with permission"""
assign_perm("authentik_core.add_group", self.login_user)
assign_perm("authentik_core.enable_group_superuser", self.login_user)
self.login_user.assign_perms_to_managed_role("authentik_core.add_group")
self.login_user.assign_perms_to_managed_role("authentik_core.enable_group_superuser")
self.client.force_login(self.login_user)
res = self.client.post(
reverse("authentik_api:group-list"),

View File

@@ -3,7 +3,6 @@
from json import loads
from django.urls import reverse
from guardian.shortcuts import assign_perm
from rest_framework.test import APITestCase
from authentik.core.tests.utils import create_test_admin_user, create_test_user
@@ -48,8 +47,8 @@ class TestImpersonation(APITestCase):
def test_impersonate_global(self):
"""Test impersonation with global permissions"""
new_user = create_test_user()
assign_perm("authentik_core.impersonate", new_user)
assign_perm("authentik_core.view_user", new_user)
new_user.assign_perms_to_managed_role("authentik_core.impersonate")
new_user.assign_perms_to_managed_role("authentik_core.view_user")
self.client.force_login(new_user)
response = self.client.post(
@@ -69,8 +68,8 @@ class TestImpersonation(APITestCase):
def test_impersonate_scoped(self):
"""Test impersonation with scoped permissions"""
new_user = create_test_user()
assign_perm("authentik_core.impersonate", new_user, self.other_user)
assign_perm("authentik_core.view_user", new_user, self.other_user)
new_user.assign_perms_to_managed_role("authentik_core.impersonate", self.other_user)
new_user.assign_perms_to_managed_role("authentik_core.view_user", self.other_user)
self.client.force_login(new_user)
response = self.client.post(

View File

@@ -39,7 +39,7 @@ def source_tester_factory(test_model: type[Source]) -> Callable:
def tester(self: TestModels):
model_class = None
if test_model._meta.abstract:
model_class = [x for x in test_model.__bases__ if issubclass(x, Source)][0]()
return
else:
model_class = test_model()
model_class.slug = "test"

View File

@@ -3,7 +3,7 @@
from django.contrib.auth.models import AnonymousUser
from django.test import TestCase
from django.urls import reverse
from guardian.utils import get_anonymous_user
from guardian.shortcuts import get_anonymous_user
from authentik.core.models import SourceUserMatchingModes, User
from authentik.core.sources.flow_manager import Action

View File

@@ -5,9 +5,10 @@ from django.test import TestCase
from authentik.core.models import Group, PropertyMapping, Source, User
from authentik.core.sources.mapper import SourceMapper
from authentik.lib.generators import generate_id
from authentik.lib.models import InternallyManagedMixin
class ProxySource(Source):
class ProxySource(InternallyManagedMixin, Source):
@property
def property_mapping_type(self):
return PropertyMapping

View File

@@ -183,16 +183,16 @@ class TestTokenAPI(APITestCase):
self.assertEqual(len(body["results"]), 1)
self.assertEqual(body["results"][0]["identifier"], token_should.identifier)
def test_list_admin(self):
"""Test Token List (Test with admin auth)"""
def test_list_with_permission(self):
"""Test Token List (Test with `view_token` permission)"""
Token.objects.all().delete()
self.client.force_login(self.admin)
token_should: Token = Token.objects.create(
identifier="test", expiring=False, user=self.user
)
token_should_not: Token = Token.objects.create(
identifier="test-2", expiring=False, user=get_anonymous_user()
)
self.user.assign_perms_to_managed_role("authentik_core.view_token")
response = self.client.get(reverse("authentik_api:token-list"))
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)

View File

@@ -1,7 +1,6 @@
"""Test Transactional API"""
from django.urls import reverse
from guardian.shortcuts import assign_perm
from rest_framework.test import APITestCase
from authentik.core.models import Application, Group
@@ -16,8 +15,8 @@ class TestTransactionalApplicationsAPI(APITestCase):
def setUp(self) -> None:
self.user = create_test_user()
assign_perm("authentik_core.add_application", self.user)
assign_perm("authentik_providers_oauth2.add_oauth2provider", self.user)
self.user.assign_perms_to_managed_role("authentik_core.add_application")
self.user.assign_perms_to_managed_role("authentik_providers_oauth2.add_oauth2provider")
def test_create_transactional(self):
"""Test transactional Application + provider creation"""
@@ -73,7 +72,7 @@ class TestTransactionalApplicationsAPI(APITestCase):
def test_create_transactional_bindings(self):
"""Test transactional Application + provider creation"""
assign_perm("authentik_policies.add_policybinding", self.user)
self.user.assign_perms_to_managed_role("authentik_policies.add_policybinding")
self.client.force_login(self.user)
uid = generate_id()
group = Group.objects.create(name=generate_id())

View File

@@ -0,0 +1,20 @@
"""user tests"""
from django.test.testcases import TestCase
from authentik.core.models import User
from authentik.lib.generators import generate_id
class TestUsers(TestCase):
"""Test user"""
def test_user_managed_role(self):
"""Test user managed role"""
perm = "authentik_core.view_user"
user = User.objects.create(username=generate_id())
user.assign_perms_to_managed_role(perm)
self.assertEqual(user.roles.count(), 1)
self.assertTrue(user.has_perm(perm))
user.remove_perms_from_managed_role(perm)
self.assertFalse(user.has_perm(perm))

View File

@@ -1,7 +1,5 @@
"""Crypto API Views"""
from datetime import datetime
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import load_pem_x509_certificate
@@ -15,14 +13,12 @@ from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
extend_schema,
extend_schema_field,
)
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
CharField,
ChoiceField,
DateTimeField,
IntegerField,
SerializerMethodField,
)
@@ -51,59 +47,15 @@ LOGGER = get_logger()
class CertificateKeyPairSerializer(ModelSerializer):
"""CertificateKeyPair Serializer"""
fingerprint_sha256 = SerializerMethodField()
fingerprint_sha1 = SerializerMethodField()
cert_expiry = SerializerMethodField()
cert_subject = SerializerMethodField()
private_key_available = SerializerMethodField()
key_type = SerializerMethodField()
certificate_download_url = SerializerMethodField()
private_key_download_url = SerializerMethodField()
@property
def _should_include_details(self) -> bool:
request: Request = self.context.get("request", None)
if not request:
return True
return str(request.query_params.get("include_details", "true")).lower() == "true"
def get_fingerprint_sha256(self, instance: CertificateKeyPair) -> str | None:
"Get certificate Hash (SHA256)"
if not self._should_include_details:
return None
return instance.fingerprint_sha256
def get_fingerprint_sha1(self, instance: CertificateKeyPair) -> str | None:
"Get certificate Hash (SHA1)"
if not self._should_include_details:
return None
return instance.fingerprint_sha1
def get_cert_expiry(self, instance: CertificateKeyPair) -> datetime | None:
"Get certificate expiry"
if not self._should_include_details:
return None
return DateTimeField().to_representation(instance.certificate.not_valid_after_utc)
def get_cert_subject(self, instance: CertificateKeyPair) -> str | None:
"""Get certificate subject as full rfc4514"""
if not self._should_include_details:
return None
return instance.certificate.subject.rfc4514_string()
def get_private_key_available(self, instance: CertificateKeyPair) -> bool:
"""Show if this keypair has a private key configured or not"""
return instance.key_data != "" and instance.key_data is not None
@extend_schema_field(ChoiceField(choices=KeyType.choices, allow_null=True))
def get_key_type(self, instance: CertificateKeyPair) -> str | None:
"""Get the key algorithm type from the certificate's public key"""
if not self._should_include_details:
return None
return instance.key_type
def get_certificate_download_url(self, instance: CertificateKeyPair) -> str:
"""Get URL to download certificate"""
return (
@@ -175,6 +127,11 @@ class CertificateKeyPairSerializer(ModelSerializer):
"managed": {"read_only": True},
"key_data": {"write_only": True},
"certificate_data": {"write_only": True},
"fingerprint_sha256": {"read_only": True},
"fingerprint_sha1": {"read_only": True},
"cert_expiry": {"read_only": True},
"cert_subject": {"read_only": True},
"key_type": {"read_only": True},
}
@@ -216,17 +173,12 @@ class CertificateKeyPairFilter(FilterSet):
return queryset.exclude(key_data__exact="")
def filter_key_type(self, queryset, name, value): # pragma: no cover
"""Filter certificates by key type using the public key from the certificate"""
"""Filter certificates by key type using the stored database field"""
if not value:
return queryset
# value is a list of KeyType enum values from MultipleChoiceFilter
filtered_pks = []
for cert in queryset:
if cert.key_type in value:
filtered_pks.append(cert.pk)
return queryset.filter(pk__in=filtered_pks)
return queryset.filter(key_type__in=value)
class Meta:
model = CertificateKeyPair
@@ -263,7 +215,6 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
"Can be specified multiple times (e.g. '?key_type=rsa&key_type=ec')"
),
),
OpenApiParameter("include_details", bool, default=True),
]
)
def list(self, request, *args, **kwargs):

View File

@@ -0,0 +1,120 @@
# Generated by Django 5.2.9 on 2025-12-09 06:22
from hashlib import md5
from cryptography.hazmat.backends import default_backend
from cryptography.x509 import load_pem_x509_certificate
from django.db import migrations, models
from authentik.crypto.signals import extract_certificate_metadata
from authentik.lib.migrations import progress_bar
def backfill_certificate_metadata(apps, schema_editor): # noqa: ARG001
"""Backfill certificate metadata and kid for existing records."""
db_alias = schema_editor.connection.alias
CertificateKeyPair = apps.get_model("authentik_crypto", "CertificateKeyPair")
print("\nStoring extra data about certificates, this might take a couple of minutes...")
for cert in progress_bar(CertificateKeyPair.objects.using(db_alias).all()):
updated_fields = []
if cert.certificate_data:
try:
certificate = load_pem_x509_certificate(
cert.certificate_data.encode("utf-8"), default_backend()
)
metadata = extract_certificate_metadata(certificate)
cert.key_type = metadata["key_type"]
cert.cert_expiry = metadata["cert_expiry"]
cert.cert_subject = metadata["cert_subject"]
cert.fingerprint_sha256 = metadata["fingerprint_sha256"]
cert.fingerprint_sha1 = metadata["fingerprint_sha1"]
updated_fields.extend(
[
"key_type",
"cert_expiry",
"cert_subject",
"fingerprint_sha256",
"fingerprint_sha1",
]
)
except (ValueError, TypeError, AttributeError):
pass
# Backfill kid with MD5 for backwards compatibility
if cert.key_data:
cert.kid = md5(cert.key_data.encode("utf-8"), usedforsecurity=False).hexdigest()
updated_fields.append("kid")
if updated_fields:
cert.save(update_fields=updated_fields, using=db_alias)
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0005_alter_certificatekeypair_options"),
]
operations = [
migrations.AddField(
model_name="certificatekeypair",
name="cert_expiry",
field=models.DateTimeField(blank=True, help_text="Certificate expiry date", null=True),
),
migrations.AddField(
model_name="certificatekeypair",
name="cert_subject",
field=models.TextField(
blank=True, help_text="Certificate subject as RFC4514 string", null=True
),
),
migrations.AddField(
model_name="certificatekeypair",
name="fingerprint_sha1",
field=models.CharField(
blank=True,
help_text="SHA1 fingerprint of the certificate",
max_length=59,
null=True,
),
),
migrations.AddField(
model_name="certificatekeypair",
name="fingerprint_sha256",
field=models.CharField(
blank=True,
help_text="SHA256 fingerprint of the certificate",
max_length=95,
null=True,
),
),
migrations.AddField(
model_name="certificatekeypair",
name="key_type",
field=models.CharField(
blank=True,
choices=[
("rsa", "RSA"),
("ec", "Elliptic Curve"),
("dsa", "DSA"),
("ed25519", "Ed25519"),
("ed448", "Ed448"),
],
help_text="Key algorithm type detected from the certificate's public key",
max_length=16,
null=True,
),
),
migrations.AddField(
model_name="certificatekeypair",
name="kid",
field=models.CharField(
blank=True, help_text="Key ID generated from private key", max_length=128, null=True
),
),
migrations.RunPython(backfill_certificate_metadata, migrations.RunPython.noop),
]

View File

@@ -1,7 +1,8 @@
"""authentik crypto models"""
from base64 import urlsafe_b64encode
from binascii import hexlify
from hashlib import md5
from hashlib import md5, sha512
from ssl import PEM_FOOTER, PEM_HEADER
from textwrap import wrap
from uuid import uuid4
@@ -47,6 +48,39 @@ def fingerprint_sha256(cert: Certificate) -> str:
return hexlify(cert.fingerprint(hashes.SHA256()), ":").decode("utf-8")
def detect_key_type(certificate: Certificate) -> str | None:
"""Detect the key algorithm type by parsing the certificate's public key"""
try:
public_key = certificate.public_key()
if isinstance(public_key, RSAPublicKey):
return KeyType.RSA
if isinstance(public_key, EllipticCurvePublicKey):
return KeyType.EC
if isinstance(public_key, DSAPublicKey):
return KeyType.DSA
if isinstance(public_key, Ed25519PublicKey):
return KeyType.ED25519
if isinstance(public_key, Ed448PublicKey):
return KeyType.ED448
except (ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("Failed to detect key type", exc=exc)
return None
def generate_key_id(key_data: str) -> str:
"""Generate Key ID using SHA512 + urlsafe_b64encode."""
if not key_data:
return ""
return urlsafe_b64encode(sha512(key_data.encode("utf-8")).digest()).decode("utf-8").rstrip("=")
def generate_key_id_legacy(key_data: str) -> str:
"""Generate Key ID using MD5 (legacy format for backwards compatibility)."""
if not key_data:
return ""
return md5(key_data.encode("utf-8")).hexdigest() # nosec
class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
"""CertificateKeyPair that can be used for signing or encrypting if `key_data`
is set, otherwise it can be used to verify remote data."""
@@ -62,6 +96,41 @@ class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
blank=True,
default="",
)
key_type = models.CharField(
max_length=16,
choices=KeyType.choices,
null=True,
blank=True,
help_text=_("Key algorithm type detected from the certificate's public key"),
)
cert_expiry = models.DateTimeField(
null=True,
blank=True,
help_text=_("Certificate expiry date"),
)
cert_subject = models.TextField(
null=True,
blank=True,
help_text=_("Certificate subject as RFC4514 string"),
)
fingerprint_sha256 = models.CharField(
max_length=95,
null=True,
blank=True,
help_text=_("SHA256 fingerprint of the certificate"),
)
fingerprint_sha1 = models.CharField(
max_length=59,
null=True,
blank=True,
help_text=_("SHA1 fingerprint of the certificate"),
)
kid = models.CharField(
max_length=128,
null=True,
blank=True,
help_text=_("Key ID generated from private key"),
)
_cert: Certificate | None = None
_private_key: PrivateKeyTypes | None = None
@@ -106,41 +175,6 @@ class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
return None
return self._private_key
@property
def fingerprint_sha256(self) -> str:
"""Get SHA256 Fingerprint of certificate_data"""
return fingerprint_sha256(self.certificate)
@property
def fingerprint_sha1(self) -> str:
"""Get SHA1 Fingerprint of certificate_data"""
return hexlify(self.certificate.fingerprint(hashes.SHA1()), ":").decode("utf-8") # nosec
@property
def kid(self):
"""Get Key ID used for JWKS"""
return (
md5(self.key_data.encode("utf-8"), usedforsecurity=False).hexdigest()
if self.key_data
else ""
) # nosec
@property
def key_type(self) -> str | None:
"""Get the key algorithm type from the certificate's public key"""
public_key = self.certificate.public_key()
if isinstance(public_key, RSAPublicKey):
return KeyType.RSA
if isinstance(public_key, EllipticCurvePublicKey):
return KeyType.EC
if isinstance(public_key, DSAPublicKey):
return KeyType.DSA
if isinstance(public_key, Ed25519PublicKey):
return KeyType.ED25519
if isinstance(public_key, Ed448PublicKey):
return KeyType.ED448
return None
def __str__(self) -> str:
return f"Certificate-Key Pair {self.name}"

View File

@@ -0,0 +1,70 @@
"""authentik crypto signals"""
from binascii import hexlify
from datetime import datetime
from ssl import CertificateError
from cryptography.hazmat.primitives import hashes
from cryptography.x509 import Certificate
from django.db.models.signals import pre_save
from django.dispatch import receiver
from structlog.stdlib import get_logger
from authentik.crypto.models import (
CertificateKeyPair,
detect_key_type,
fingerprint_sha256,
generate_key_id,
generate_key_id_legacy,
)
LOGGER = get_logger()
def extract_certificate_metadata(certificate: Certificate) -> dict[str, str | datetime]:
"""Extract all metadata fields from a certificate."""
metadata = {}
try:
metadata["key_type"] = detect_key_type(certificate)
metadata["cert_expiry"] = certificate.not_valid_after_utc
metadata["cert_subject"] = certificate.subject.rfc4514_string()
metadata["fingerprint_sha256"] = fingerprint_sha256(certificate)
metadata["fingerprint_sha1"] = hexlify(
certificate.fingerprint(hashes.SHA1()), ":" # nosec
).decode("utf-8")
except (ValueError, TypeError, AttributeError) as exc:
raise CertificateError(f"Invalid certificate metadata: {exc}") from exc
return metadata
@receiver(pre_save, sender="authentik_crypto.CertificateKeyPair")
def certificate_key_pair_pre_save(
sender: type[CertificateKeyPair], instance: CertificateKeyPair, **_
):
"""Automatically populate certificate metadata fields before saving"""
# Only extract metadata if certificate_data is present
if not instance.certificate_data:
return
try:
metadata = extract_certificate_metadata(instance.certificate)
except (CertificateError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("Failed to extract certificate metadata", exc=exc)
return
instance.key_type = metadata["key_type"]
instance.cert_expiry = metadata["cert_expiry"]
instance.cert_subject = metadata["cert_subject"]
instance.fingerprint_sha256 = metadata["fingerprint_sha256"]
instance.fingerprint_sha1 = metadata["fingerprint_sha1"]
# Generate kid if not set, or regenerate if key_data has changed
# Preserve existing kid (MD5 or SHA512) if it matches the current key_data
if instance.key_data:
new_kid = generate_key_id(instance.key_data)
legacy_kid = generate_key_id_legacy(instance.key_data)
if instance.kid not in (new_kid, legacy_kid):
instance.kid = new_kid

View File

@@ -9,7 +9,6 @@ from cryptography.x509.extensions import SubjectAlternativeName
from cryptography.x509.general_name import DNSName
from django.urls import reverse
from django.utils.timezone import now
from guardian.shortcuts import assign_perm
from rest_framework.test import APITestCase
from authentik.core.api.used_by import DeleteAction
@@ -21,7 +20,7 @@ from authentik.core.tests.utils import (
)
from authentik.crypto.api import CertificateKeyPairSerializer
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair
from authentik.crypto.models import CertificateKeyPair, generate_key_id, generate_key_id_legacy
from authentik.crypto.tasks import MANAGED_DISCOVERED, certificate_discovery
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id, generate_key
@@ -174,28 +173,31 @@ class TestCrypto(APITestCase):
self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
self.assertEqual(api_cert["fingerprint_sha256"], cert.fingerprint_sha256)
def test_list_without_details(self):
"""Test API List (no details)"""
def test_list_always_includes_details(self):
"""Test API List always includes certificate details"""
cert = create_test_cert()
self.client.force_login(create_test_admin_user())
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-list",
),
data={"name": cert.name, "include_details": False},
data={"name": cert.name},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
self.assertEqual(api_cert["fingerprint_sha1"], None)
self.assertEqual(api_cert["fingerprint_sha256"], None)
# All details should now always be included
self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
self.assertEqual(api_cert["fingerprint_sha256"], cert.fingerprint_sha256)
self.assertIsNotNone(api_cert["cert_expiry"])
self.assertIsNotNone(api_cert["cert_subject"])
def test_certificate_download(self):
"""Test certificate export (download)"""
keypair = create_test_cert()
user = create_test_user()
assign_perm("view_certificatekeypair", user, keypair)
assign_perm("view_certificatekeypair_certificate", user, keypair)
user.assign_perms_to_managed_role("view_certificatekeypair", keypair)
user.assign_perms_to_managed_role("view_certificatekeypair_certificate", keypair)
self.client.force_login(user)
response = self.client.get(
reverse(
@@ -218,8 +220,8 @@ class TestCrypto(APITestCase):
"""Test private_key export (download)"""
keypair = create_test_cert()
user = create_test_user()
assign_perm("view_certificatekeypair", user, keypair)
assign_perm("view_certificatekeypair_key", user, keypair)
user.assign_perms_to_managed_role("view_certificatekeypair", keypair)
user.assign_perms_to_managed_role("view_certificatekeypair_key", keypair)
self.client.force_login(user)
response = self.client.get(
reverse(
@@ -427,3 +429,114 @@ class TestCrypto(APITestCase):
self.assertEqual(
1, final_count, "Should not create duplicate cert for same private key"
)
def test_metadata_extraction_with_cert_and_key(self):
"""Test that metadata is extracted when creating keypair with certificate and key"""
cert = create_test_cert()
# Verify all metadata fields are populated
self.assertIsNotNone(cert.key_type)
self.assertIsNotNone(cert.cert_expiry)
self.assertIsNotNone(cert.cert_subject)
self.assertIsNotNone(cert.fingerprint_sha256)
self.assertIsNotNone(cert.fingerprint_sha1)
# Verify kid is generated using SHA512 for new records
self.assertIsNotNone(cert.kid)
self.assertEqual(cert.kid, generate_key_id(cert.key_data))
def test_metadata_extraction_without_key(self):
"""Test that metadata is extracted when creating keypair without private key"""
builder = CertificateBuilder(generate_id())
builder.build(subject_alt_names=[], validity_days=3)
# Create keypair with only certificate, no key
cert = CertificateKeyPair.objects.create(
name=generate_id(),
certificate_data=builder.certificate,
key_data="",
)
# Verify certificate metadata fields are populated
self.assertIsNotNone(cert.key_type)
self.assertIsNotNone(cert.cert_expiry)
self.assertIsNotNone(cert.cert_subject)
self.assertIsNotNone(cert.fingerprint_sha256)
self.assertIsNotNone(cert.fingerprint_sha1)
# Verify kid is empty when no key_data
self.assertEqual(cert.kid, None)
def test_metadata_extraction_invalid_cert(self):
"""Test that invalid certificate data doesn't crash, just skips metadata"""
cert = CertificateKeyPair.objects.create(
name=generate_id(),
certificate_data="invalid certificate data",
key_data="",
)
# Verify metadata fields are None for invalid cert
self.assertIsNone(cert.key_type)
self.assertIsNone(cert.cert_expiry)
self.assertIsNone(cert.cert_subject)
self.assertIsNone(cert.fingerprint_sha256)
self.assertIsNone(cert.fingerprint_sha1)
self.assertIsNone(cert.kid)
def test_kid_legacy_preservation(self):
"""Test that legacy MD5 kid is preserved when key_data hasn't changed"""
cert = create_test_cert()
# Simulate a legacy MD5 kid (as if backfilled from old system)
legacy_kid = generate_key_id_legacy(cert.key_data)
CertificateKeyPair.objects.filter(pk=cert.pk).update(kid=legacy_kid)
cert.refresh_from_db()
self.assertEqual(cert.kid, legacy_kid)
# Save the cert again (e.g., name change) - kid should be preserved
cert.name = generate_id()
cert.save()
cert.refresh_from_db()
self.assertEqual(cert.kid, legacy_kid)
def test_kid_regenerated_on_key_change(self):
"""Test that kid is regenerated when key_data changes"""
cert = create_test_cert()
original_kid = cert.kid
# Generate a new key and update the keypair
builder = CertificateBuilder(generate_id())
builder.build(subject_alt_names=[], validity_days=3)
cert.key_data = builder.private_key
cert.certificate_data = builder.certificate
cert.save()
cert.refresh_from_db()
# Kid should be regenerated for the new key
self.assertNotEqual(cert.kid, original_kid)
self.assertEqual(cert.kid, generate_key_id(cert.key_data))
def test_kid_regenerated_on_key_change_from_legacy(self):
"""Test that kid is regenerated from legacy MD5 when key_data changes"""
cert = create_test_cert()
# Simulate a legacy MD5 kid
legacy_kid = generate_key_id_legacy(cert.key_data)
CertificateKeyPair.objects.filter(pk=cert.pk).update(kid=legacy_kid)
cert.refresh_from_db()
self.assertEqual(cert.kid, legacy_kid)
# Generate a new key and update the keypair
builder = CertificateBuilder(generate_id())
builder.build(subject_alt_names=[], validity_days=3)
cert.key_data = builder.private_key
cert.certificate_data = builder.certificate
cert.save()
cert.refresh_from_db()
# Kid should now be SHA512 for the new key
self.assertNotEqual(cert.kid, legacy_kid)
self.assertEqual(cert.kid, generate_key_id(cert.key_data))

View File

@@ -20,6 +20,7 @@ class DeviceUserBindingSerializer(PolicyBindingSerializer):
class DeviceUserBindingViewSet(PolicyBindingViewSet):
"""PolicyBinding Viewset"""
serializer_class = DeviceUserBindingSerializer
queryset = (
DeviceUserBinding.objects.all()
.select_related("target", "group", "user")

View File

@@ -1,14 +1,13 @@
from typing import cast
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import (
CharField,
ChoiceField,
)
from rest_framework.fields import ChoiceField
from rest_framework.permissions import IsAuthenticated
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request
from rest_framework.response import Response
@@ -25,7 +24,11 @@ from authentik.endpoints.connectors.agent.api.agent import (
from authentik.endpoints.connectors.agent.auth import (
AgentAuth,
AgentEnrollmentAuth,
DeviceAuthFedAuthentication,
agent_auth_issue_token,
check_device_policies,
)
from authentik.endpoints.connectors.agent.controller import MDMConfigResponseSerializer
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
@@ -34,7 +37,10 @@ from authentik.endpoints.connectors.agent.models import (
)
from authentik.endpoints.facts import DeviceFacts, OSFamily
from authentik.endpoints.models import Device
from authentik.events.models import Event, EventAction
from authentik.flows.planner import PLAN_CONTEXT_DEVICE
from authentik.lib.utils.reflection import ConditionalInheritance
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
class AgentConnectorSerializer(ConnectorSerializer):
@@ -74,11 +80,6 @@ class MDMConfigSerializer(PassiveSerializer):
return token
class MDMConfigResponseSerializer(PassiveSerializer):
config = CharField(required=True)
class AgentConnectorViewSet(
ConditionalInheritance(
"authentik.enterprise.endpoints.connectors.agent.api.connectors.AgentConnectorViewSetMixin"
@@ -108,7 +109,7 @@ class AgentConnectorViewSet(
raise PermissionDenied()
ctrl = connector.controller(connector)
payload = ctrl.generate_mdm_config(data.validated_data["platform"], request, token)
return Response({"config": payload})
return Response(payload.validated_data)
@extend_schema(
request=EnrollSerializer(),
@@ -170,3 +171,43 @@ class AgentConnectorViewSet(
connection: AgentDeviceConnection = token.device
connection.create_snapshot(data.validated_data)
return Response(status=204)
@extend_schema(
request=OpenApiTypes.NONE,
parameters=[OpenApiParameter("device", OpenApiTypes.STR, location="query", required=True)],
responses={
200: AgentTokenResponseSerializer(),
404: OpenApiResponse(description="Device not found"),
},
)
@action(
methods=["POST"],
detail=False,
pagination_class=None,
filter_backends=[],
permission_classes=[IsAuthenticated],
authentication_classes=[DeviceAuthFedAuthentication],
)
def auth_fed(self, request: Request) -> Response:
federated_token, device, connector = request.auth
policy_result = check_device_policies(device, federated_token.user, request._request)
if not policy_result.passing:
raise ValidationError(
{"policy_result": "Policy denied access", "policy_messages": policy_result.messages}
)
token, exp = agent_auth_issue_token(device, connector, federated_token.user)
rel_exp = int((exp - now()).total_seconds())
Event.new(
EventAction.LOGIN,
**{
PLAN_CONTEXT_METHOD: "jwt",
PLAN_CONTEXT_METHOD_ARGS: {
"jwt": federated_token,
"provider": federated_token.provider,
},
PLAN_CONTEXT_DEVICE: device,
},
).from_http(request, user=federated_token.user)
return Response({"token": token, "expires_in": rel_exp})

View File

@@ -1,9 +1,11 @@
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.tokens import TokenViewSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
@@ -19,6 +21,11 @@ class EnrollmentTokenSerializer(ModelSerializer):
source="device_group", read_only=True, required=False
)
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
self.fields["key"] = CharField(required=False)
class Meta:
model = EnrollmentToken
fields = [

View File

@@ -1,13 +1,28 @@
from typing import Any
from django.http import HttpRequest
from django.utils.timezone import now
from drf_spectacular.extensions import OpenApiAuthenticationExtension
from jwt import PyJWTError, decode, encode
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request
from structlog.stdlib import get_logger
from authentik.api.authentication import IPCUser, validate_auth
from authentik.core.middleware import CTX_AUTH_VIA
from authentik.core.models import User
from authentik.endpoints.connectors.agent.models import DeviceToken, EnrollmentToken
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceToken, EnrollmentToken
from authentik.endpoints.models import Device
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBindingModel
from authentik.providers.oauth2.models import AccessToken, JWTAlgorithms, OAuth2Provider
LOGGER = get_logger()
PLATFORM_ISSUER = "goauthentik.io/platform"
class DeviceUser(IPCUser):
@@ -40,3 +55,96 @@ class AgentAuth(BaseAuthentication):
raise PermissionDenied()
CTX_AUTH_VIA.set("endpoint_token")
return (DeviceUser(), device_token)
def agent_auth_issue_token(device: Device, connector: AgentConnector, user: User, **kwargs):
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
if not kp:
return None, None
exp = now() + timedelta_from_string(connector.auth_session_duration)
token = encode(
{
"iss": PLATFORM_ISSUER,
"aud": str(device.pk),
"iat": int(now().timestamp()),
"exp": int(exp.timestamp()),
"preferred_username": user.username,
**kwargs,
},
kp.private_key,
headers={
"kid": kp.kid,
},
algorithm=JWTAlgorithms.from_private_key(kp.private_key),
)
return token, exp
class DeviceAuthFedAuthentication(BaseAuthentication):
def authenticate(self, request):
raw_token = validate_auth(get_authorization_header(request))
if not raw_token:
LOGGER.warning("Missing token")
return None
device = Device.filter_not_expired(name=request.query_params.get("device")).first()
if not device:
LOGGER.warning("Couldn't find device")
return None
connectors_for_device = AgentConnector.objects.filter(device__in=[device])
connector = connectors_for_device.first()
providers = OAuth2Provider.objects.filter(agentconnector__in=connectors_for_device)
federated_token = AccessToken.objects.filter(
token=raw_token, provider__in=providers
).first()
if not federated_token:
LOGGER.warning("Couldn't lookup provider")
return None
_key, _alg = federated_token.provider.jwt_key
try:
decode(
raw_token,
_key.public_key(),
algorithms=[_alg],
options={
"verify_aud": False,
},
)
LOGGER.info(
"successfully verified JWT with provider", provider=federated_token.provider.name
)
return (federated_token.user, (federated_token, device, connector))
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to verify JWT", exc=exc, provider=federated_token.provider.name)
return None
class DeviceFederationAuthSchema(OpenApiAuthenticationExtension):
"""Auth schema"""
target_class = DeviceAuthFedAuthentication
name = "device_federation"
def get_security_definition(self, auto_schema):
"""Auth schema"""
return {"type": "http", "scheme": "bearer"}
def check_device_policies(device: Device, user: User, request: HttpRequest):
"""Check policies bound to device group and device"""
if device.access_group:
result = check_pbm_policies(device.access_group, user, request)
if result.passing:
return result
return check_pbm_policies(device, user, request)
def check_pbm_policies(pbm: PolicyBindingModel, user: User, request: HttpRequest):
policy_engine = PolicyEngine(pbm, user, request)
policy_engine.use_cache = False
policy_engine.empty_result = False
policy_engine.mode = pbm.policy_engine_mode
policy_engine.build()
result = policy_engine.result
LOGGER.debug("PolicyAccessView user_has_access", user=user.username, result=result, pbm=pbm.pk)
return result

View File

@@ -4,7 +4,9 @@ from xml.etree.ElementTree import Element, SubElement, tostring # nosec
from django.http import HttpRequest
from django.urls import reverse
from rest_framework.fields import CharField
from authentik.core.api.utils import PassiveSerializer
from authentik.endpoints.connectors.agent.models import AgentConnector, EnrollmentToken
from authentik.endpoints.controller import BaseController
from authentik.endpoints.facts import OSFamily
@@ -33,6 +35,13 @@ def csp_create_replace_item(loc_uri, data_value) -> Element:
return replace
class MDMConfigResponseSerializer(PassiveSerializer):
config = CharField(required=True)
mime_type = CharField(required=True)
filename = CharField(required=True)
class AgentConnectorController(BaseController[AgentConnector]):
def supported_enrollment_methods(self):
@@ -40,14 +49,20 @@ class AgentConnectorController(BaseController[AgentConnector]):
def generate_mdm_config(
self, target_platform: OSFamily, request: HttpRequest, token: EnrollmentToken
) -> str:
) -> MDMConfigResponseSerializer:
response = None
if target_platform == OSFamily.windows:
return self._generate_mdm_config_windows(request, token)
response = self._generate_mdm_config_windows(request, token)
if target_platform in [OSFamily.iOS, OSFamily.macOS]:
return self._generate_mdm_config_macos(request, token)
raise ValueError(f"Unsupported platform for MDM Configuration: {target_platform}")
response = self._generate_mdm_config_macos(request, token)
if not response:
raise ValueError(f"Unsupported platform for MDM Configuration: {target_platform}")
response.is_valid(raise_exception=True)
return response
def _generate_mdm_config_windows(self, request: HttpRequest, token: EnrollmentToken) -> str:
def _generate_mdm_config_windows(
self, request: HttpRequest, token: EnrollmentToken
) -> MDMConfigResponseSerializer:
base_uri = (
"./Vendor/MSFT/Registry/HKLM/SOFTWARE/authentik Security Inc./Platform/ManagedConfig"
)
@@ -61,9 +76,17 @@ class AgentConnectorController(BaseController[AgentConnector]):
)
payload = tostring(token_item, encoding="unicode") + tostring(url_item, encoding="unicode")
return payload
return MDMConfigResponseSerializer(
data={
"config": payload,
"mime_type": "application/xml",
"filename": f"{self.connector.name}_config.csp.xml",
}
)
def _generate_mdm_config_macos(self, request: HttpRequest, token: EnrollmentToken) -> str:
def _generate_mdm_config_macos(
self, request: HttpRequest, token: EnrollmentToken
) -> MDMConfigResponseSerializer:
token_uuid = str(token.pk).upper()
payload = dumps(
{
@@ -130,4 +153,10 @@ class AgentConnectorController(BaseController[AgentConnector]):
},
fmt=PlistFormat.FMT_XML,
).decode()
return payload
return MDMConfigResponseSerializer(
data={
"config": payload,
"mime_type": "application/xml",
"filename": f"{self.connector.name}_config.mobileconfig",
}
)

View File

@@ -16,7 +16,7 @@ from authentik.endpoints.models import (
)
from authentik.flows.stage import StageView
from authentik.lib.generators import generate_key
from authentik.lib.models import SerializerModel
from authentik.lib.models import InternallyManagedMixin, SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
if TYPE_CHECKING:
@@ -97,7 +97,7 @@ class AgentDeviceUserBinding(DeviceUserBinding):
apple_enclave_key_id = models.TextField()
class DeviceToken(ExpiringModel):
class DeviceToken(InternallyManagedMixin, ExpiringModel):
"""Per-device token used for authentication."""
token_uuid = models.UUIDField(primary_key=True, default=uuid4)
@@ -143,7 +143,7 @@ class EnrollmentToken(ExpiringModel, SerializerModel):
]
class DeviceAuthenticationToken(ExpiringModel):
class DeviceAuthenticationToken(InternallyManagedMixin, ExpiringModel):
identifier = models.UUIDField(default=uuid4, primary_key=True)
device = models.ForeignKey(Device, on_delete=models.CASCADE)
@@ -160,7 +160,7 @@ class DeviceAuthenticationToken(ExpiringModel):
verbose_name_plural = _("Device authentication tokens")
class AppleNonce(ExpiringModel):
class AppleNonce(InternallyManagedMixin, ExpiringModel):
nonce = models.TextField()
device_token = models.ForeignKey(DeviceToken, on_delete=models.CASCADE)

View File

@@ -23,8 +23,8 @@ class TestAgentConnector(APITestCase):
res = self.connector.controller(self.connector).generate_mdm_config(
OSFamily.macOS, request, self.token
)
self.assertIsNotNone(res)
data = loads(res, fmt=PlistFormat.FMT_XML)
self.assertIsNotNone(res.validated_data)
data = loads(res.validated_data["config"], fmt=PlistFormat.FMT_XML)
self.assertEqual(data["PayloadContent"][0]["RegistrationToken"], self.token.key)
self.assertEqual(data["PayloadContent"][0]["URL"], "http://testserver/")
@@ -33,7 +33,8 @@ class TestAgentConnector(APITestCase):
res = self.connector.controller(self.connector).generate_mdm_config(
OSFamily.windows, request, self.token
)
self.assertIsNotNone(res)
fromstring(f"<root>{res}</root>")
self.assertIn(self.token.key, res)
self.assertIn("http://testserver/", res)
self.assertIsNotNone(res.validated_data)
config = res.validated_data["config"]
fromstring(f"<root>{config}</root>")
self.assertIn(self.token.key, config)
self.assertIn("http://testserver/", config)

View File

@@ -15,7 +15,7 @@ from authentik.core.models import AttributesMixin, ExpiringModel
from authentik.flows.models import Stage
from authentik.flows.stage import StageView
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.lib.models import InheritanceForeignKey, InternallyManagedMixin, SerializerModel
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
from authentik.policies.models import PolicyBinding, PolicyBindingModel
from authentik.tasks.schedules.common import ScheduleSpec
@@ -28,7 +28,7 @@ LOGGER = get_logger()
DEVICE_FACTS_CACHE_TIMEOUT = 3600
class Device(ExpiringModel, AttributesMixin, PolicyBindingModel):
class Device(InternallyManagedMixin, ExpiringModel, AttributesMixin, PolicyBindingModel):
device_uuid = models.UUIDField(default=uuid4, primary_key=True)
name = models.TextField(unique=True)
@@ -86,7 +86,7 @@ class DeviceUserBinding(PolicyBinding):
verbose_name_plural = _("Device User bindings")
class DeviceConnection(SerializerModel):
class DeviceConnection(InternallyManagedMixin, SerializerModel):
device_connection_uuid = models.UUIDField(default=uuid4, primary_key=True)
device = models.ForeignKey("Device", on_delete=models.CASCADE)
connector = models.ForeignKey("Connector", on_delete=models.CASCADE)
@@ -115,7 +115,7 @@ class DeviceConnection(SerializerModel):
verbose_name_plural = _("Device connections")
class DeviceFactSnapshot(ExpiringModel, SerializerModel):
class DeviceFactSnapshot(InternallyManagedMixin, ExpiringModel, SerializerModel):
snapshot_id = models.UUIDField(primary_key=True, default=uuid4)
connection = models.ForeignKey(DeviceConnection, on_delete=models.CASCADE)
data = models.JSONField(default=dict)
@@ -175,7 +175,7 @@ class Connector(ScheduledModel, SerializerModel):
]
class DeviceAccessGroup(PolicyBindingModel):
class DeviceAccessGroup(SerializerModel, PolicyBindingModel):
name = models.TextField(unique=True)

View File

@@ -1,6 +1,8 @@
"""Enterprise API Views"""
from collections.abc import Callable
from datetime import timedelta
from functools import wraps
from django.utils.timezone import now
from django.utils.translation import gettext as _
@@ -35,6 +37,18 @@ class EnterpriseRequiredMixin:
return super().validate(attrs)
def enterprise_action(func: Callable):
"""Check permissions for a single custom action"""
@wraps(func)
def wrapper(*args, **kwargs) -> Response:
if not LicenseKey.cached_summary().status.is_valid:
raise ValidationError(_("Enterprise is required to use this endpoint."))
return func(*args, **kwargs)
return wrapper
class LicenseSerializer(ModelSerializer):
"""License Serializer"""

View File

@@ -1,31 +1,20 @@
from django.urls import reverse
from django.utils.timezone import now
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from structlog.stdlib import get_logger
from authentik.endpoints.connectors.agent.api.agent import (
AgentAuthenticationResponse,
AgentTokenResponseSerializer,
)
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.endpoints.connectors.agent.models import (
DeviceAuthenticationToken,
DeviceToken,
)
from authentik.enterprise.endpoints.connectors.agent.auth import (
DeviceAuthFedAuthentication,
agent_auth_issue_token,
check_device_policies,
)
from authentik.events.models import Event, EventAction
from authentik.flows.planner import PLAN_CONTEXT_DEVICE
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
from authentik.enterprise.api import enterprise_action
LOGGER = get_logger()
@@ -37,6 +26,7 @@ class AgentConnectorViewSetMixin:
responses=AgentAuthenticationResponse(),
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
@enterprise_action
def auth_ia(self, request: Request) -> Response:
token: DeviceToken = request.auth
auth_token = DeviceAuthenticationToken.objects.create(
@@ -54,43 +44,3 @@ class AgentConnectorViewSetMixin:
),
}
)
@extend_schema(
request=OpenApiTypes.NONE,
parameters=[OpenApiParameter("device", OpenApiTypes.STR, location="query", required=True)],
responses={
200: AgentTokenResponseSerializer(),
404: OpenApiResponse(description="Device not found"),
},
)
@action(
methods=["POST"],
detail=False,
pagination_class=None,
filter_backends=[],
permission_classes=[IsAuthenticated],
authentication_classes=[DeviceAuthFedAuthentication],
)
def auth_fed(self, request: Request) -> Response:
federated_token, device, connector = request.auth
policy_result = check_device_policies(device, federated_token.user, request._request)
if not policy_result.passing:
raise ValidationError(
{"policy_result": "Policy denied access", "policy_messages": policy_result.messages}
)
token, exp = agent_auth_issue_token(device, connector, federated_token.user)
rel_exp = int((exp - now()).total_seconds())
Event.new(
EventAction.LOGIN,
**{
PLAN_CONTEXT_METHOD: "jwt",
PLAN_CONTEXT_METHOD_ARGS: {
"jwt": federated_token,
"provider": federated_token.provider,
},
PLAN_CONTEXT_DEVICE: device,
},
).from_http(request, user=federated_token.user)
return Response({"token": token, "expires_in": rel_exp})

View File

@@ -1,113 +0,0 @@
from django.http import HttpRequest
from django.utils.timezone import now
from drf_spectacular.extensions import OpenApiAuthenticationExtension
from jwt import PyJWTError, decode, encode
from rest_framework.authentication import BaseAuthentication
from structlog.stdlib import get_logger
from authentik.api.authentication import get_authorization_header, validate_auth
from authentik.core.models import User
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import AgentConnector
from authentik.endpoints.models import Device
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBindingModel
from authentik.providers.oauth2.models import AccessToken, JWTAlgorithms, OAuth2Provider
LOGGER = get_logger()
PLATFORM_ISSUER = "goauthentik.io/platform"
def agent_auth_issue_token(device: Device, connector: AgentConnector, user: User, **kwargs):
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
if not kp:
return None, None
exp = now() + timedelta_from_string(connector.auth_session_duration)
token = encode(
{
"iss": PLATFORM_ISSUER,
"aud": str(device.pk),
"iat": int(now().timestamp()),
"exp": int(exp.timestamp()),
"preferred_username": user.username,
**kwargs,
},
kp.private_key,
headers={
"kid": kp.kid,
},
algorithm=JWTAlgorithms.from_private_key(kp.private_key),
)
return token, exp
class DeviceAuthFedAuthentication(BaseAuthentication):
def authenticate(self, request):
raw_token = validate_auth(get_authorization_header(request))
if not raw_token:
LOGGER.warning("Missing token")
return None
device = Device.filter_not_expired(name=request.query_params.get("device")).first()
if not device:
LOGGER.warning("Couldn't find device")
return None
connectors_for_device = AgentConnector.objects.filter(device__in=[device])
connector = connectors_for_device.first()
providers = OAuth2Provider.objects.filter(agentconnector__in=connectors_for_device)
federated_token = AccessToken.objects.filter(
token=raw_token, provider__in=providers
).first()
if not federated_token:
LOGGER.warning("Couldn't lookup provider")
return None
_key, _alg = federated_token.provider.jwt_key
try:
decode(
raw_token,
_key.public_key(),
algorithms=[_alg],
options={
"verify_aud": False,
},
)
LOGGER.info(
"successfully verified JWT with provider", provider=federated_token.provider.name
)
return (federated_token.user, (federated_token, device, connector))
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to verify JWT", exc=exc, provider=federated_token.provider.name)
return None
class DeviceFederationAuthSchema(OpenApiAuthenticationExtension):
"""Auth schema"""
target_class = DeviceAuthFedAuthentication
name = "device_federation"
def get_security_definition(self, auto_schema):
"""Auth schema"""
return {"type": "http", "scheme": "bearer"}
def check_device_policies(device: Device, user: User, request: HttpRequest):
"""Check policies bound to device group and device"""
if device.access_group:
result = check_pbm_policies(device.access_group, user, request)
if result.passing:
return result
return check_pbm_policies(device, user, request)
def check_pbm_policies(pbm: PolicyBindingModel, user: User, request: HttpRequest):
policy_engine = PolicyEngine(pbm, user, request)
policy_engine.use_cache = False
policy_engine.empty_result = False
policy_engine.mode = pbm.policy_engine_mode
policy_engine.build()
result = policy_engine.result
LOGGER.debug("PolicyAccessView user_has_access", user=user.username, result=result, pbm=pbm.pk)
return result

View File

@@ -16,6 +16,7 @@ from authentik.endpoints.connectors.agent.models import (
EnrollmentToken,
)
from authentik.endpoints.models import Device
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import JWTAlgorithms
@@ -106,3 +107,9 @@ class TestAppleToken(TestCase):
)
self.assertEqual(res.status_code, 200)
event = Event.objects.filter(
action=EventAction.LOGIN,
app="authentik.endpoints.connectors.agent",
).first()
self.assertIsNotNone(event)
self.assertEqual(event.context["device"]["name"], self.device.name)

View File

@@ -63,8 +63,21 @@ class TestConnectorAuthIA(FlowTestCase):
)
self.assertEqual(response.status_code, 200)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@reconcile_app("authentik_crypto")
def test_auth_ia_fulfill(self):
License.objects.create(key=generate_id())
self.client.force_login(self.user)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-ia"),

View File

@@ -24,6 +24,7 @@ from authentik.endpoints.connectors.agent.models import (
from authentik.enterprise.endpoints.connectors.agent.http import JWEResponse
from authentik.events.models import Event, EventAction
from authentik.events.signals import SESSION_LOGIN_EVENT
from authentik.flows.planner import PLAN_CONTEXT_DEVICE
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.constants import TOKEN_TYPE
from authentik.providers.oauth2.id_token import IDToken
@@ -125,7 +126,13 @@ class TokenView(View):
return device_user, decoded
def create_auth_session(self, user: User):
event = Event.new(EventAction.LOGIN).from_http(self.request, user=user)
event = Event.new(
EventAction.LOGIN,
app="authentik.endpoints.connectors.agent",
**{
PLAN_CONTEXT_DEVICE: self.device_connection.device,
},
).from_http(self.request, user=user)
store = SessionStore()
store[SESSION_LOGIN_EVENT] = event
store.save()

View File

@@ -3,12 +3,12 @@ from hmac import compare_digest
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseBadRequest, QueryDict
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceAuthenticationToken
from authentik.endpoints.models import Device
from authentik.enterprise.endpoints.connectors.agent.auth import (
from authentik.endpoints.connectors.agent.auth import (
agent_auth_issue_token,
check_device_policies,
)
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceAuthenticationToken
from authentik.endpoints.models import Device
from authentik.enterprise.policy import EnterprisePolicyAccessView
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage
@@ -60,6 +60,8 @@ class AgentInteractiveAuth(EnterprisePolicyAccessView):
device_token_hash, sha256(self.auth_token.device_token.key.encode()).hexdigest()
):
return HttpResponseBadRequest("Invalid device token")
if not self.connector.authorization_flow:
return HttpResponseBadRequest("No authorization flow configured")
planner = FlowPlanner(self.connector.authorization_flow)
planner.allow_empty_flows = True

View File

@@ -11,7 +11,7 @@ from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from authentik.core.models import ExpiringModel
from authentik.lib.models import SerializerModel
from authentik.lib.models import InternallyManagedMixin, SerializerModel
if TYPE_CHECKING:
from authentik.enterprise.license import LicenseKey
@@ -81,7 +81,7 @@ class LicenseUsageStatus(models.TextChoices):
return self in [LicenseUsageStatus.VALID, LicenseUsageStatus.EXPIRY_SOON]
class LicenseUsage(ExpiringModel):
class LicenseUsage(InternallyManagedMixin, ExpiringModel):
"""a single license usage record"""
expires = models.DateTimeField(default=usage_expiry)

View File

@@ -18,7 +18,7 @@ from authentik.core.models import (
User,
UserTypes,
)
from authentik.lib.models import SerializerModel
from authentik.lib.models import InternallyManagedMixin, SerializerModel
from authentik.lib.sync.outgoing.base import BaseOutgoingSyncClient
from authentik.lib.sync.outgoing.models import OutgoingSyncDeleteAction, OutgoingSyncProvider
@@ -32,7 +32,7 @@ def default_scopes() -> list[str]:
]
class GoogleWorkspaceProviderUser(SerializerModel):
class GoogleWorkspaceProviderUser(InternallyManagedMixin, SerializerModel):
"""Mapping of a user and provider to a Google user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@@ -58,7 +58,7 @@ class GoogleWorkspaceProviderUser(SerializerModel):
return f"Google Workspace Provider User {self.user_id} to {self.provider_id}"
class GoogleWorkspaceProviderGroup(SerializerModel):
class GoogleWorkspaceProviderGroup(InternallyManagedMixin, SerializerModel):
"""Mapping of a group and provider to a Google group ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)

View File

@@ -18,12 +18,12 @@ from authentik.core.models import (
User,
UserTypes,
)
from authentik.lib.models import SerializerModel
from authentik.lib.models import InternallyManagedMixin, SerializerModel
from authentik.lib.sync.outgoing.base import BaseOutgoingSyncClient
from authentik.lib.sync.outgoing.models import OutgoingSyncDeleteAction, OutgoingSyncProvider
class MicrosoftEntraProviderUser(SerializerModel):
class MicrosoftEntraProviderUser(InternallyManagedMixin, SerializerModel):
"""Mapping of a user and provider to a Microsoft user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@@ -49,7 +49,7 @@ class MicrosoftEntraProviderUser(SerializerModel):
return f"Microsoft Entra Provider User {self.user_id} to {self.provider_id}"
class MicrosoftEntraProviderGroup(SerializerModel):
class MicrosoftEntraProviderGroup(InternallyManagedMixin, SerializerModel):
"""Mapping of a group and provider to a Microsoft group ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)

View File

@@ -14,7 +14,7 @@ from jwt import encode
from authentik.core.models import BackchannelProvider, ExpiringModel, Token
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.models import CreatedUpdatedModel
from authentik.lib.models import CreatedUpdatedModel, InternallyManagedMixin
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider
from authentik.tasks.models import TasksModel
@@ -153,7 +153,7 @@ class Stream(models.Model):
return encode(data, key, algorithm=alg, headers=headers)
class StreamEvent(CreatedUpdatedModel, ExpiringModel):
class StreamEvent(InternallyManagedMixin, CreatedUpdatedModel, ExpiringModel):
"""Single stream event to be sent"""
uuid = models.UUIDField(default=uuid4, primary_key=True, editable=False)

Some files were not shown because too many files have changed in this diff Show More